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
|
|
@ -294,10 +294,11 @@ pub trait Intel400xControlPins {
|
|||
}
|
||||
|
||||
/// Read RESET pin state
|
||||
/// Hardware: RESET is active LOW (pin LOW = reset active)
|
||||
fn read_reset_pin(&self) -> bool {
|
||||
if let Ok(pin) = self.get_base().get_pin("RESET") {
|
||||
if let Ok(pin_guard) = pin.lock() {
|
||||
pin_guard.read() == PinValue::High
|
||||
pin_guard.read() == PinValue::Low // Active LOW: LOW means reset is active
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
|
@ -313,10 +314,11 @@ pub trait Intel400xResetHandling {
|
|||
|
||||
/// Handle system reset signal
|
||||
/// Hardware: RESET pin clears all internal state and tri-states outputs
|
||||
/// Note: RESET is active LOW (pin LOW = reset active)
|
||||
fn handle_reset(&mut self, reset_pin_name: &str) -> bool {
|
||||
let reset = if let Ok(pin) = self.get_base().get_pin(reset_pin_name) {
|
||||
if let Ok(pin_guard) = pin.lock() {
|
||||
pin_guard.read() == PinValue::High
|
||||
pin_guard.read() == PinValue::Low // Active LOW: LOW means reset is active
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ pub struct Intel4004 {
|
|||
current_instruction: u8, // Currently executing instruction
|
||||
address_latch: u8, // Latched address for memory operations
|
||||
#[allow(dead_code)] // Reserved for future memory operations implementation
|
||||
data_latch: u8, // Latched data for memory operations
|
||||
data_latch: u8, // Latched data for memory operations
|
||||
clock_speed: f64, // Target clock speed in Hz
|
||||
rom_port: u8, // Currently selected ROM port (0-15)
|
||||
ram_bank: u8, // Currently selected RAM bank (0-7)
|
||||
|
|
|
|||
|
|
@ -95,6 +95,11 @@ impl Intel400xResetHandling for Intel4001 {
|
|||
self.io_direction = [IoDirection::Input; 4];
|
||||
self.selected_io_port = None;
|
||||
self.io_mode = IoMode::Input; // Reset I/O mode to Input
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1212,7 +1217,10 @@ mod tests {
|
|||
|
||||
// Should have started memory operation and transitioned to AddressPhase
|
||||
// because SYNC=1, CM=1, CI=0 triggers chip select on PHI1 rising edge
|
||||
assert_eq!(rom.state_machine.current_state, Intel400xUnifiedState::AddressPhase);
|
||||
assert_eq!(
|
||||
rom.state_machine.current_state,
|
||||
Intel400xUnifiedState::AddressPhase
|
||||
);
|
||||
println!(
|
||||
"Memory state after Φ1 rising: {:?}",
|
||||
rom.state_machine.current_state
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -81,6 +81,11 @@ impl Intel400xResetHandling for Intel4003 {
|
|||
self.input_buffer = 0;
|
||||
self.output_buffer = 0;
|
||||
self.serial_mode = SerialMode::Hold;
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -367,18 +372,14 @@ impl Intel4003 {
|
|||
/// Handle Φ1 falling edge - End of address phase
|
||||
/// Hardware: Φ1 low = End of CPU address driving phase
|
||||
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
|
||||
// Intel 4003 ignores address transmission (responds_to_address() = false)
|
||||
// No address latching needed for shift register operations
|
||||
}
|
||||
|
||||
/// Handle Φ2 rising edge - Data phase
|
||||
/// Hardware: Φ2 high = Peripherals drive bus with data
|
||||
/// Focus: Data driving operations
|
||||
fn handle_phi2_rising(&mut self) {
|
||||
println!(
|
||||
"DEBUG: [SHIFT] Φ2 rising edge detected, current state: {:?}",
|
||||
self.state_machine.get_state()
|
||||
);
|
||||
// Execute current state logic
|
||||
self.execute_state_machine();
|
||||
}
|
||||
|
|
@ -388,10 +389,6 @@ impl Intel4003 {
|
|||
/// 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: [SHIFT] Φ2 falling edge detected, current state: {:?}",
|
||||
self.state_machine.get_state()
|
||||
);
|
||||
match self.state_machine.get_state() {
|
||||
Intel400xUnifiedState::ShiftData | Intel400xUnifiedState::LoadData => {
|
||||
// End of data phase - tri-state bus and return to idle
|
||||
|
|
@ -417,10 +414,6 @@ impl Intel4003 {
|
|||
/// Hardware: CPU provides 8-bit address as two 4-bit nibbles
|
||||
/// Note: Intel 4003 mostly ignores address transmission for shift operations
|
||||
fn handle_address_latching(&mut self) {
|
||||
println!(
|
||||
"DEBUG: handle_address_latching called, current state: {:?}",
|
||||
self.state_machine.current_state
|
||||
);
|
||||
// Intel 4003 mostly ignores address transmission for shift operations
|
||||
// But we still need to handle the address nibble for compatibility
|
||||
let nibble = self.read_data_bus() & 0x0F; // Only 4 bits are valid for address nibbles
|
||||
|
|
@ -436,18 +429,12 @@ impl Intel4003 {
|
|||
fn handle_shift_operation(&mut self) {
|
||||
// Read data from bus
|
||||
let data = self.read_data_bus() & 0x0F; // Only 4 bits are valid
|
||||
println!("DEBUG: [SHIFT] Shifting data 0x{:X} into register", data);
|
||||
|
||||
// Shift data into register
|
||||
self.shift_register = ((self.shift_register << 4) | (data as u16)) & 0x03FF; // Keep only 10 bits
|
||||
|
||||
// Update output buffer with shifted-out bit
|
||||
self.output_buffer = ((self.shift_register >> 9) & 1) as u8;
|
||||
|
||||
println!(
|
||||
"DEBUG: [SHIFT] Register after shift: 0x{:03X}, Output bit: {}",
|
||||
self.shift_register, self.output_buffer
|
||||
);
|
||||
}
|
||||
|
||||
/// Handle shift register load operation
|
||||
|
|
@ -455,18 +442,12 @@ impl Intel4003 {
|
|||
fn handle_load_operation(&mut self) {
|
||||
// Read data from bus
|
||||
let data = self.read_data_bus() & 0x0F; // Only 4 bits are valid
|
||||
println!("DEBUG: [SHIFT] Loading data 0x{:X} into register", data);
|
||||
|
||||
// Load data into register
|
||||
self.shift_register = (self.shift_register & 0xFF00) | (data as u16); // Keep lower 8 bits
|
||||
|
||||
// Update output buffer with loaded data
|
||||
self.output_buffer = data;
|
||||
|
||||
println!(
|
||||
"DEBUG: [SHIFT] Register after load: 0x{:03X}, Output: 0x{:X}",
|
||||
self.shift_register, self.output_buffer
|
||||
);
|
||||
}
|
||||
|
||||
/// Helper function to process latency elapsed event
|
||||
|
|
@ -549,11 +530,19 @@ impl Component for Intel4003 {
|
|||
/// 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;
|
||||
}
|
||||
|
||||
// Check for reset first (highest priority, independent of clock edges)
|
||||
// This matches Intel 4001 behavior where reset is processed immediately
|
||||
let reset_active = self.read_reset_pin();
|
||||
if reset_active {
|
||||
self.perform_reset();
|
||||
// Skip further processing if reset is active
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle both rising and falling edges for proper two-phase operation
|
||||
let phi1_rising = self.is_phi1_rising_edge(self.prev_phi1);
|
||||
let phi1_falling = self.is_phi1_falling_edge(self.prev_phi1);
|
||||
|
|
@ -569,7 +558,6 @@ impl Component for Intel4003 {
|
|||
let mut events = Vec::new();
|
||||
|
||||
if phi1_rising {
|
||||
println!("DEBUG: phi1_rising detected");
|
||||
events.push(Intel400xEvent::ClockPhi1Rising);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue