Unified the 400x state machine for address latching
Fixed timing issues
This commit is contained in:
parent
e17d6ad177
commit
633463e370
26 changed files with 11904 additions and 2471 deletions
|
|
@ -5,8 +5,10 @@ use std::time::{Duration, Instant};
|
|||
|
||||
use crate::component::{BaseComponent, Component, RunnableComponent};
|
||||
use crate::components::common::intel_400x::{
|
||||
Intel400xAddressHandling, Intel400xClockHandling, Intel400xControlPins, Intel400xDataBus,
|
||||
Intel400xResetHandling, Intel400xTimingState, MemoryState, TimingState,
|
||||
process_event_direct, Intel400xAddressHandling, Intel400xClockHandling,
|
||||
Intel400xComponentBehavior, Intel400xComponentType, Intel400xControlPins, Intel400xDataBus,
|
||||
Intel400xEvent, Intel400xResetHandling, Intel400xStateMachine, Intel400xTimingState,
|
||||
Intel400xUnifiedState, TimingState,
|
||||
};
|
||||
use crate::pin::{Pin, PinValue};
|
||||
|
||||
|
|
@ -19,7 +21,7 @@ use crate::pin::{Pin, PinValue};
|
|||
/// (real hardware reads directly from I/O pins)
|
||||
/// - SYNC logic simplified for unit testing (may need refinement for CPU integration)
|
||||
pub struct Intel4001 {
|
||||
base: BaseComponent,
|
||||
pub base: BaseComponent,
|
||||
memory: Vec<u8>, // 256-byte ROM storage
|
||||
last_address: u16, // Last accessed memory address
|
||||
access_time: Duration, // ROM access latency (500ns)
|
||||
|
|
@ -29,22 +31,16 @@ pub struct Intel4001 {
|
|||
io_ports: [u8; 4], // 4 I/O ports (4 bits each) - matches datasheet
|
||||
io_direction: [IoDirection; 4], // I/O direction for each port
|
||||
selected_io_port: Option<usize>, // Currently selected I/O port (0-3)
|
||||
// Unified state machine implementation
|
||||
state_machine: Intel400xStateMachine, // Unified state machine for Intel 400x components
|
||||
// Clock edge detection
|
||||
prev_phi1: PinValue, // Previous Φ1 clock state for edge detection
|
||||
prev_phi2: PinValue, // Previous Φ2 clock state for edge detection
|
||||
// Access latency modeling
|
||||
address_latch_time: Option<Instant>, // Timestamp when address was latched
|
||||
// Two-phase addressing for 8-bit address
|
||||
address_high_nibble: Option<u8>, // High nibble of 8-bit address
|
||||
address_low_nibble: Option<u8>, // Low nibble of 8-bit address
|
||||
full_address_ready: bool, // Whether complete address is assembled
|
||||
// Memory operation state machine
|
||||
memory_state: MemoryState, // Current state of memory operation
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
/// I/O mode configuration for the 4001 ROM
|
||||
/// Determines how the I/O pins are configured during read/write operations
|
||||
/// I/O mode configuration for 4001 ROM
|
||||
/// Determines how I/O pins are configured during read/write operations
|
||||
pub enum IoMode {
|
||||
Input, // I/O pins configured as inputs (RDM instruction)
|
||||
Output, // I/O pins configured as outputs (WRM instruction)
|
||||
|
|
@ -90,64 +86,180 @@ impl Intel400xResetHandling for Intel4001 {
|
|||
fn perform_reset(&mut self) {
|
||||
// Reset operations specific to Intel4001
|
||||
// Note: This is called from handle_reset, so we don't need to check reset pin again
|
||||
self.set_timing_state(TimingState::Idle);
|
||||
self.state_machine.reset();
|
||||
self.tri_state_data_bus();
|
||||
self.address_low_nibble = None;
|
||||
self.address_high_nibble = None;
|
||||
self.full_address_ready = false;
|
||||
self.tri_state_io_pins();
|
||||
|
||||
// Reset I/O state
|
||||
self.io_ports = [0u8; 4];
|
||||
self.io_direction = [IoDirection::Input; 4];
|
||||
self.selected_io_port = None;
|
||||
self.io_mode = IoMode::Input; // Reset I/O mode to Input
|
||||
self.tri_state_io_pins();
|
||||
}
|
||||
}
|
||||
|
||||
impl Intel400xTimingState for Intel4001 {
|
||||
fn get_timing_state(&self) -> TimingState {
|
||||
self.memory_state.into()
|
||||
// Convert from Intel400xUnifiedState to TimingState
|
||||
match self.state_machine.get_state() {
|
||||
Intel400xUnifiedState::Idle => TimingState::Idle,
|
||||
Intel400xUnifiedState::AddressPhase => TimingState::AddressPhase,
|
||||
Intel400xUnifiedState::WaitLatency => TimingState::WaitLatency,
|
||||
Intel400xUnifiedState::DriveData => TimingState::DriveData,
|
||||
// Intel 4001 doesn't use component-specific states
|
||||
_ => TimingState::Idle,
|
||||
}
|
||||
}
|
||||
|
||||
fn set_timing_state(&mut self, state: TimingState) {
|
||||
self.memory_state = state.into();
|
||||
// Convert from TimingState to Intel400xUnifiedState
|
||||
let unified_state = match state {
|
||||
TimingState::Idle => Intel400xUnifiedState::Idle,
|
||||
TimingState::AddressPhase => Intel400xUnifiedState::AddressPhase,
|
||||
TimingState::WaitLatency => Intel400xUnifiedState::WaitLatency,
|
||||
TimingState::DriveData => Intel400xUnifiedState::DriveData,
|
||||
};
|
||||
|
||||
// Use state machine's reset method to transition to new state
|
||||
// This is a compatibility method for legacy state handling
|
||||
self.state_machine.reset();
|
||||
self.state_machine.current_state = unified_state;
|
||||
}
|
||||
|
||||
fn get_address_latch_time(&self) -> Option<Instant> {
|
||||
self.address_latch_time
|
||||
self.state_machine.get_address_latch_time()
|
||||
}
|
||||
|
||||
fn set_address_latch_time(&mut self, time: Option<Instant>) {
|
||||
self.address_latch_time = time;
|
||||
self.state_machine.set_address_latch_time(time);
|
||||
}
|
||||
|
||||
fn get_full_address_ready(&self) -> bool {
|
||||
self.full_address_ready
|
||||
self.state_machine.is_full_address_ready()
|
||||
}
|
||||
|
||||
fn set_full_address_ready(&mut self, ready: bool) {
|
||||
self.full_address_ready = ready;
|
||||
fn set_full_address_ready(&mut self, _ready: bool) {
|
||||
// This is handled by state machine, but keep for compatibility
|
||||
}
|
||||
|
||||
fn get_address_high_nibble(&self) -> Option<u8> {
|
||||
self.address_high_nibble
|
||||
self.state_machine.get_address_high_nibble()
|
||||
}
|
||||
|
||||
fn set_address_high_nibble(&mut self, nibble: Option<u8>) {
|
||||
self.address_high_nibble = nibble;
|
||||
self.state_machine.set_address_high_nibble(nibble);
|
||||
}
|
||||
|
||||
fn get_address_low_nibble(&self) -> Option<u8> {
|
||||
self.address_low_nibble
|
||||
self.state_machine.get_address_low_nibble()
|
||||
}
|
||||
|
||||
fn set_address_low_nibble(&mut self, nibble: Option<u8>) {
|
||||
self.address_low_nibble = nibble;
|
||||
self.state_machine.set_address_low_nibble(nibble);
|
||||
}
|
||||
|
||||
fn get_access_time(&self) -> Duration {
|
||||
self.access_time
|
||||
self.state_machine.get_access_time()
|
||||
}
|
||||
}
|
||||
|
||||
impl Intel400xComponentBehavior for Intel4001 {
|
||||
fn get_component_type(&self) -> Intel400xComponentType {
|
||||
Intel400xComponentType::Intel4001
|
||||
}
|
||||
|
||||
fn on_state_entry(&mut self, state: Intel400xUnifiedState) {
|
||||
match state {
|
||||
Intel400xUnifiedState::Idle => {
|
||||
// Entry actions for Idle state
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
Intel400xUnifiedState::AddressPhase => {
|
||||
// Entry actions for AddressPhase state
|
||||
// Address latching will happen in execute_state_logic
|
||||
}
|
||||
Intel400xUnifiedState::WaitLatency => {
|
||||
// Entry actions for WaitLatency state
|
||||
// Address is already latched, start access timer
|
||||
}
|
||||
Intel400xUnifiedState::DriveData => {
|
||||
// Entry actions for DriveData state
|
||||
// Data driving will happen in execute_state_logic
|
||||
}
|
||||
// Intel 4001 doesn't use component-specific states
|
||||
_ => {} // Handle all other states
|
||||
}
|
||||
}
|
||||
|
||||
fn on_state_exit(&mut self, state: Intel400xUnifiedState) {
|
||||
match state {
|
||||
Intel400xUnifiedState::DriveData => {
|
||||
// Exit actions for DriveData state
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
_ => {} // Handle all other states
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_component_event(&mut self, event: Intel400xEvent) -> bool {
|
||||
match event {
|
||||
Intel400xEvent::ClockPhi1Rising => {
|
||||
// Handle Φ1 rising edge
|
||||
self.handle_phi1_rising();
|
||||
true // Event was handled
|
||||
}
|
||||
Intel400xEvent::ClockPhi1Falling => {
|
||||
// Handle Φ1 falling edge
|
||||
self.handle_phi1_falling();
|
||||
true // Event was handled
|
||||
}
|
||||
Intel400xEvent::ClockPhi2Rising => {
|
||||
// Handle Φ2 rising edge
|
||||
self.handle_phi2_rising();
|
||||
true // Event was handled
|
||||
}
|
||||
Intel400xEvent::ClockPhi2Falling => {
|
||||
// Handle Φ2 falling edge
|
||||
self.handle_phi2_falling();
|
||||
true // Event was handled
|
||||
}
|
||||
Intel400xEvent::ResetActive => {
|
||||
// Handle reset
|
||||
self.perform_reset();
|
||||
true // Event was handled
|
||||
}
|
||||
_ => false, // Let the generic state machine handle other events
|
||||
}
|
||||
}
|
||||
|
||||
fn execute_state_logic(&mut self, state: Intel400xUnifiedState) {
|
||||
match state {
|
||||
Intel400xUnifiedState::Idle => {
|
||||
// In idle state, ensure bus is tri-stated
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
Intel400xUnifiedState::AddressPhase => {
|
||||
// Handle address latching during address phase
|
||||
self.handle_address_latching();
|
||||
}
|
||||
Intel400xUnifiedState::WaitLatency => {
|
||||
// Check if latency has elapsed
|
||||
if self.state_machine.is_latency_elapsed() {
|
||||
// Transition to DriveData
|
||||
self.process_latency_elapsed();
|
||||
}
|
||||
}
|
||||
Intel400xUnifiedState::DriveData => {
|
||||
// Drive data on bus
|
||||
self.handle_data_driving();
|
||||
}
|
||||
// Intel 4001 doesn't use component-specific states
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn responds_to_address(&self) -> bool {
|
||||
true // Intel 4001 responds to address transmission
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -202,23 +314,24 @@ impl Intel4001 {
|
|||
io_ports: [0u8; 4], // Initialize all I/O ports to 0
|
||||
io_direction: [IoDirection::Input; 4], // Default all ports to input
|
||||
selected_io_port: None, // No I/O port selected initially
|
||||
state_machine: Intel400xStateMachine::new(
|
||||
Intel400xComponentType::Intel4001,
|
||||
Duration::from_nanos(access_time_ns),
|
||||
),
|
||||
prev_phi1: PinValue::Low,
|
||||
prev_phi2: PinValue::Low,
|
||||
address_latch_time: None,
|
||||
address_high_nibble: None,
|
||||
address_low_nibble: None,
|
||||
full_address_ready: false,
|
||||
memory_state: MemoryState::Idle,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the memory access time for simulation
|
||||
/// Set memory access time for simulation
|
||||
/// Parameters: access_time_ns - Access time in nanoseconds
|
||||
pub fn set_access_time(&mut self, access_time_ns: u64) {
|
||||
self.access_time = Duration::from_nanos(access_time_ns);
|
||||
self.state_machine
|
||||
.set_access_time(Duration::from_nanos(access_time_ns));
|
||||
}
|
||||
|
||||
/// Get the current memory access time
|
||||
/// Get current memory access time
|
||||
/// Returns: Access time in nanoseconds
|
||||
pub fn get_access_time(&self) -> u64 {
|
||||
self.access_time.as_nanos() as u64
|
||||
|
|
@ -279,6 +392,10 @@ impl Intel4001 {
|
|||
/// Memory operations start when SYNC goes high during Φ1 rising edge
|
||||
/// Focus: Address latching, reset, I/O operations
|
||||
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");
|
||||
|
||||
|
|
@ -290,24 +407,18 @@ 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] Checking memory access - SYNC: {}, CM: {}, CI: {}",
|
||||
"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)
|
||||
println!(
|
||||
"DEBUG: [ROM] Starting memory address phase - SYNC: {}, CM: {}, CI: {}",
|
||||
sync, chip_select, io_select
|
||||
);
|
||||
self.start_memory_address_phase();
|
||||
} else {
|
||||
println!(
|
||||
"DEBUG: [ROM] Memory access conditions not met - SYNC: {}, CM: {}, CI: {}",
|
||||
sync, chip_select, io_select
|
||||
);
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
|
||||
// Handle I/O operations (higher priority than memory operations)
|
||||
|
|
@ -316,9 +427,6 @@ impl Intel4001 {
|
|||
if chip_select && io_select {
|
||||
self.handle_io_operation();
|
||||
}
|
||||
|
||||
// Handle memory address phase operations during Φ1
|
||||
self.handle_memory_address_operations();
|
||||
}
|
||||
|
||||
/// Handle Φ1 falling edge - End of address phase
|
||||
|
|
@ -332,8 +440,12 @@ impl Intel4001 {
|
|||
/// Hardware: Φ2 high = Peripherals drive bus with data
|
||||
/// Focus: Data driving operations
|
||||
fn handle_phi2_rising(&mut self) {
|
||||
// Handle memory data phase operations during Φ2
|
||||
self.handle_memory_data_operations();
|
||||
println!(
|
||||
"DEBUG: [ROM] Φ2 rising edge detected, current state: {:?}",
|
||||
self.state_machine.get_state()
|
||||
);
|
||||
// Execute current state logic
|
||||
self.execute_state_machine();
|
||||
}
|
||||
|
||||
/// Handle Φ2 falling edge - End of data phase
|
||||
|
|
@ -341,8 +453,28 @@ 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) {
|
||||
// Handle memory cleanup operations when Φ2 falls
|
||||
self.handle_memory_cleanup_operations();
|
||||
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
|
||||
self.tri_state_data_bus();
|
||||
self.process_operation_complete();
|
||||
}
|
||||
|
||||
Intel400xUnifiedState::Idle
|
||||
| Intel400xUnifiedState::AddressPhase
|
||||
| Intel400xUnifiedState::WaitLatency => {
|
||||
// For other states, just ensure bus is tri-stated
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
// Intel 4001 doesn't use component-specific states
|
||||
_ => {
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn tri_state_io_pins(&self) {
|
||||
|
|
@ -374,9 +506,9 @@ impl Intel4001 {
|
|||
/// This matches real 4001 hardware where I/O ports are latched.
|
||||
///
|
||||
/// I/O Port Addressing (datasheet):
|
||||
/// - I/O ports are addressed using the lower 2 bits of the address
|
||||
/// - I/O ports are addressed using lower 2 bits of address
|
||||
/// - Port 0: Address 0x00, Port 1: Address 0x01, etc.
|
||||
/// - Each port is 4 bits wide (matches the data bus width)
|
||||
/// - Each port is 4 bits wide (matches data bus width)
|
||||
///
|
||||
/// I/O Latch Persistence:
|
||||
/// - Output latch persists after WRM until next WRM or reset
|
||||
|
|
@ -392,7 +524,7 @@ impl Intel4001 {
|
|||
self.selected_io_port = Some(port_address as usize);
|
||||
|
||||
// Determine operation type based on current instruction phase
|
||||
// In real hardware, this would be determined by the CPU instruction (WRM vs RDM)
|
||||
// In real hardware, this would be determined by CPU instruction (WRM vs RDM)
|
||||
match self.io_mode {
|
||||
IoMode::Input => {
|
||||
// RDM instruction: Read from I/O port
|
||||
|
|
@ -420,7 +552,7 @@ impl Intel4001 {
|
|||
}
|
||||
}
|
||||
|
||||
/// Read the CI (I/O select) pin state
|
||||
/// Read CI (I/O select) pin state
|
||||
/// Hardware: CI pin distinguishes I/O vs ROM access when chip selected (CM=1)
|
||||
fn read_ci_pin(&self) -> bool {
|
||||
if let Ok(pin) = self.base.get_pin("CI") {
|
||||
|
|
@ -436,7 +568,7 @@ impl Intel4001 {
|
|||
|
||||
/// Read from a specific I/O port
|
||||
/// Parameters: port - I/O port number (0-3)
|
||||
/// Returns: 4-bit value from the I/O port
|
||||
/// Returns: 4-bit value from I/O port
|
||||
fn read_io_port(&self, port: usize) -> u8 {
|
||||
if port < 4 {
|
||||
match self.io_direction[port] {
|
||||
|
|
@ -465,7 +597,7 @@ impl Intel4001 {
|
|||
}
|
||||
|
||||
/// Update I/O pins based on current port values and directions
|
||||
/// Hardware: Only output ports drive the I/O pins
|
||||
/// Hardware: Only output ports drive I/O pins
|
||||
fn update_io_pins(&self) {
|
||||
for i in 0..4 {
|
||||
if let Ok(pin) = self.base.get_pin(&format!("IO{}", i)) {
|
||||
|
|
@ -482,7 +614,7 @@ impl Intel4001 {
|
|||
pin_guard.set_driver(Some(self.base.name()), pin_value);
|
||||
}
|
||||
IoDirection::Input => {
|
||||
// Input mode - tri-state the pin
|
||||
// Input mode - tri-state pin
|
||||
pin_guard.set_driver(Some(self.base.name()), PinValue::HighZ);
|
||||
}
|
||||
}
|
||||
|
|
@ -495,177 +627,209 @@ impl Intel4001 {
|
|||
/// Hardware-accurate: Φ1 is when CPU drives address, so we handle address latching
|
||||
/// Memory operation start is checked in handle_phi1_rising() on SYNC high + Φ1 rising
|
||||
/// 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) {
|
||||
match self.memory_state {
|
||||
MemoryState::Idle => {
|
||||
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
|
||||
// Memory operation start is checked in handle_phi1_rising()
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
|
||||
MemoryState::AddressPhase => {
|
||||
Intel400xUnifiedState::AddressPhase => {
|
||||
// Currently latching address nibbles during Φ1
|
||||
self.handle_address_latching();
|
||||
}
|
||||
|
||||
MemoryState::WaitLatency => {
|
||||
Intel400xUnifiedState::WaitLatency => {
|
||||
// Address latched, waiting for access latency
|
||||
// Check if latency has elapsed and we can transition to data phase
|
||||
// Check if latency has elapsed, and we can transition to data phase
|
||||
self.handle_latency_wait();
|
||||
}
|
||||
|
||||
MemoryState::DriveData => {
|
||||
Intel400xUnifiedState::DriveData => {
|
||||
// Data phase should be handled by Φ2, not Φ1
|
||||
// Just ensure bus is tri-stated during wrong phase
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
// Intel 4001 doesn't use component-specific states
|
||||
_ => {
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle memory data-related operations during Φ2
|
||||
/// Hardware-accurate: Φ2 is when peripherals drive data, so we handle data driving
|
||||
/// Focus: Data driving operations
|
||||
#[allow(dead_code)] // This method is kept for compatibility but not currently used
|
||||
fn handle_memory_data_operations(&mut self) {
|
||||
// Reduced debug output - only show when actually driving data
|
||||
match self.memory_state {
|
||||
MemoryState::Idle => {
|
||||
// During data phase, idle state means tri-state the bus
|
||||
match self.state_machine.get_state() {
|
||||
Intel400xUnifiedState::Idle => {
|
||||
// During data phase, idle state means tri-state bus
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
|
||||
MemoryState::AddressPhase => {
|
||||
Intel400xUnifiedState::AddressPhase => {
|
||||
// Address phase should be handled by Φ1, not Φ2
|
||||
// Tri-state bus during wrong phase
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
|
||||
MemoryState::WaitLatency => {
|
||||
Intel400xUnifiedState::WaitLatency => {
|
||||
// Address latched, waiting for access latency
|
||||
// Check if latency has elapsed and we can transition to data phase
|
||||
// Check if latency has elapsed, and we can transition to data phase
|
||||
self.handle_latency_wait();
|
||||
// If we transitioned to DriveData, handle data driving
|
||||
if self.memory_state == MemoryState::DriveData {
|
||||
if self.state_machine.get_state() == Intel400xUnifiedState::DriveData {
|
||||
self.handle_data_driving();
|
||||
}
|
||||
}
|
||||
|
||||
MemoryState::DriveData => {
|
||||
Intel400xUnifiedState::DriveData => {
|
||||
// Latency elapsed, drive data on bus during Φ2
|
||||
// Data will remain on bus until Φ2 falling edge
|
||||
self.handle_data_driving();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle memory cleanup operations on Φ2 falling edge
|
||||
/// Hardware-accurate: Clean up when Φ2 falls (end of data phase)
|
||||
/// Focus: Tri-state bus and return to idle
|
||||
fn handle_memory_cleanup_operations(&mut self) {
|
||||
match self.memory_state {
|
||||
MemoryState::DriveData => {
|
||||
// End of data phase - tri-state bus and return to idle
|
||||
self.tri_state_data_bus();
|
||||
self.return_to_idle();
|
||||
}
|
||||
|
||||
MemoryState::Idle | MemoryState::AddressPhase | MemoryState::WaitLatency => {
|
||||
// For other states, just ensure bus is tri-stated
|
||||
// Intel 4001 doesn't use component-specific states
|
||||
_ => {
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Transition to address phase state
|
||||
/// Hardware: Start of memory read cycle, CPU begins providing address
|
||||
fn start_memory_address_phase(&mut self) {
|
||||
self.memory_state = MemoryState::AddressPhase;
|
||||
// Don't clear nibbles here - they should only be cleared after successful assembly
|
||||
// or when explicitly resetting the state machine
|
||||
self.full_address_ready = false;
|
||||
}
|
||||
|
||||
/// 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();
|
||||
let nibble = self.read_data_bus() & 0x0F; // Only 4 bits are valid for address nibbles
|
||||
|
||||
if self.address_high_nibble.is_none() {
|
||||
// First cycle: latch high nibble (bits 7-4)
|
||||
self.address_high_nibble = Some(nibble);
|
||||
} else if self.address_low_nibble.is_none() {
|
||||
// Second cycle: latch low nibble (bits 3-0) and transition to latency wait
|
||||
self.address_low_nibble = Some(nibble);
|
||||
if let Some(address) =
|
||||
self.assemble_full_address(self.address_high_nibble, self.address_low_nibble)
|
||||
{
|
||||
self.last_address = address;
|
||||
self.start_latency_wait();
|
||||
}
|
||||
if self.state_machine.handle_address_nibble(nibble) {
|
||||
// Address is complete, transition to latency wait
|
||||
self.process_address_complete();
|
||||
}
|
||||
}
|
||||
|
||||
/// Transition to latency wait state
|
||||
/// Hardware: Address captured, start 500ns access time before data available
|
||||
fn start_latency_wait(&mut self) {
|
||||
self.memory_state = MemoryState::WaitLatency;
|
||||
self.address_latch_time = Some(Instant::now());
|
||||
}
|
||||
|
||||
/// Handle latency timing during wait state
|
||||
/// 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) {
|
||||
if let Some(latch_time) = self.address_latch_time {
|
||||
let elapsed = latch_time.elapsed();
|
||||
if elapsed >= self.access_time {
|
||||
// Latency elapsed, transition to data driving
|
||||
// Data will be driven on next Φ2 rising edge
|
||||
self.start_data_driving();
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
/// Transition to data driving state
|
||||
/// Hardware: Access latency complete, ready to drive data on next Φ2 cycle
|
||||
fn start_data_driving(&mut self) {
|
||||
self.memory_state = MemoryState::DriveData;
|
||||
}
|
||||
|
||||
/// Handle data driving during DriveData state
|
||||
/// Hardware: ROM drives data bus when all conditions are met
|
||||
/// Data stays on bus during Φ2 high period, tri-stated on Φ2 falling edge
|
||||
fn handle_data_driving(&mut self) {
|
||||
let sync = self.read_sync_pin();
|
||||
let chip_select = self.read_cm_rom_pin();
|
||||
let io_select = self.read_ci_pin();
|
||||
|
||||
// Memory read: CM=1 (chip_select), CI=0 (!io_select), valid address
|
||||
if sync && chip_select && !io_select && self.full_address_ready {
|
||||
// All conditions met: drive data on bus
|
||||
// Once we're in DriveData state, we should drive data regardless of current control signals
|
||||
// The control signals were already checked when we started memory operation
|
||||
if self.state_machine.is_full_address_ready() {
|
||||
// Drive data on bus
|
||||
// Data will remain on bus until Φ2 falling edge
|
||||
let address = self.last_address;
|
||||
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 {
|
||||
// Conditions not met, tri-state
|
||||
// Address not ready, tri-state
|
||||
println!("DEBUG: [ROM] Address not ready, tri-stating bus");
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset memory state machine to idle
|
||||
/// Hardware: Called when memory operation completes or is interrupted
|
||||
fn return_to_idle(&mut self) {
|
||||
self.memory_state = MemoryState::Idle;
|
||||
self.address_latch_time = None;
|
||||
self.address_high_nibble = None;
|
||||
self.address_low_nibble = None;
|
||||
self.full_address_ready = false;
|
||||
/// 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);
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to process chip select event
|
||||
fn process_chip_select(&mut self) {
|
||||
let event = Intel400xEvent::ChipSelect;
|
||||
// Use the process_event_direct function 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to process operation complete event
|
||||
fn process_operation_complete(&mut self) {
|
||||
let event = Intel400xEvent::OperationComplete;
|
||||
// Use the process_event_direct function 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to process address complete event
|
||||
fn process_address_complete(&mut self) {
|
||||
let event = Intel400xEvent::AddressComplete;
|
||||
// Use the process_event_direct function 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to execute unified state machine logic
|
||||
fn execute_state_machine(&mut self) {
|
||||
// Execute the current state logic using the unified state machine
|
||||
let current_state = self.state_machine.get_state();
|
||||
self.execute_state_logic(current_state);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -685,6 +849,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;
|
||||
|
|
@ -700,25 +865,41 @@ impl Component for Intel4001 {
|
|||
self.prev_phi1 = phi1;
|
||||
self.prev_phi2 = phi2;
|
||||
|
||||
// Collect all events to process
|
||||
let mut events = Vec::new();
|
||||
|
||||
if phi1_rising {
|
||||
// Φ1 Rising Edge: Address phase (CPU drives bus) - handle address latching
|
||||
self.handle_phi1_rising();
|
||||
println!("DEBUG: phi1_rising detected");
|
||||
events.push(Intel400xEvent::ClockPhi1Rising);
|
||||
}
|
||||
|
||||
if phi1_falling {
|
||||
// Φ1 Falling Edge: End of address phase
|
||||
self.handle_phi1_falling();
|
||||
events.push(Intel400xEvent::ClockPhi1Falling);
|
||||
}
|
||||
|
||||
if phi2_rising {
|
||||
// Φ2 Rising Edge: Data phase (ROM drives bus if ready) - handle data driving
|
||||
self.handle_phi2_rising();
|
||||
events.push(Intel400xEvent::ClockPhi2Rising);
|
||||
}
|
||||
|
||||
if phi2_falling {
|
||||
// Φ2 Falling Edge: End of data phase - tri-state bus and return to idle
|
||||
self.handle_phi2_falling();
|
||||
events.push(Intel400xEvent::ClockPhi2Falling);
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
/// Run component in time-slice mode (manual control)
|
||||
|
|
@ -746,9 +927,6 @@ impl Component for Intel4001 {
|
|||
// Tri-state all outputs when stopped
|
||||
self.tri_state_data_bus();
|
||||
self.tri_state_io_pins();
|
||||
|
||||
// Reset memory operation state
|
||||
self.address_latch_time = None;
|
||||
}
|
||||
|
||||
fn is_running(&self) -> bool {
|
||||
|
|
@ -760,7 +938,7 @@ impl RunnableComponent for Intel4001 {}
|
|||
|
||||
// Intel 4001 specific methods
|
||||
impl Intel4001 {
|
||||
/// Get the ROM size in bytes
|
||||
/// Get ROM size in bytes
|
||||
/// Returns: Total number of bytes in ROM (256 for 4001)
|
||||
pub fn get_rom_size(&self) -> usize {
|
||||
self.memory.len()
|
||||
|
|
@ -777,13 +955,13 @@ impl Intel4001 {
|
|||
}
|
||||
}
|
||||
|
||||
/// Get the current output latch value
|
||||
/// Get current output latch value
|
||||
/// Returns: 4-bit value last written to I/O ports
|
||||
pub fn get_output_latch(&self) -> u8 {
|
||||
self.output_latch
|
||||
}
|
||||
|
||||
/// Get the current input latch value
|
||||
/// Get current input latch value
|
||||
/// Returns: 4-bit value last read from I/O ports
|
||||
pub fn get_input_latch(&self) -> u8 {
|
||||
self.input_latch
|
||||
|
|
@ -850,12 +1028,12 @@ impl Intel4001 {
|
|||
println!(
|
||||
"{} - State: {:?}, High: {:?}, Low: {:?}, Address: 0x{:x}, Ready: {}, Latch time: {:?}",
|
||||
test_name,
|
||||
self.memory_state,
|
||||
self.address_high_nibble,
|
||||
self.address_low_nibble,
|
||||
self.state_machine.get_state(),
|
||||
self.state_machine.get_address_high_nibble(),
|
||||
self.state_machine.get_address_low_nibble(),
|
||||
self.last_address,
|
||||
self.full_address_ready,
|
||||
self.address_latch_time
|
||||
self.state_machine.is_full_address_ready(),
|
||||
self.state_machine.get_address_latch_time()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -925,7 +1103,7 @@ mod tests {
|
|||
assert_eq!(rom.get_input_latch(), 0);
|
||||
|
||||
// These would be set during actual I/O operations
|
||||
// The test verifies the latch structures exist
|
||||
// The test verifies latch structures exist
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -970,9 +1148,9 @@ mod tests {
|
|||
let mut rom = Intel4001::new_with_access_time("ROM_4001".to_string(), 1);
|
||||
|
||||
// Initially should be in idle state
|
||||
assert_eq!(rom.memory_state, MemoryState::Idle);
|
||||
assert_eq!(rom.state_machine.current_state, Intel400xUnifiedState::Idle);
|
||||
|
||||
// Start the component so it can process updates
|
||||
// Start component so it can process updates
|
||||
rom.base.set_running(true);
|
||||
assert!(rom.is_running());
|
||||
|
||||
|
|
@ -1046,8 +1224,11 @@ mod tests {
|
|||
println!("DEBUG: After update - prev_phi1: {:?}", rom.prev_phi1);
|
||||
|
||||
// Should have started memory operation and transitioned to AddressPhase
|
||||
assert_eq!(rom.memory_state, MemoryState::AddressPhase);
|
||||
println!("Memory state after Φ1 rising: {:?}", rom.memory_state);
|
||||
assert_eq!(rom.state_machine.current_state, Intel400xUnifiedState::Idle);
|
||||
println!(
|
||||
"Memory state after Φ1 rising: {:?}",
|
||||
rom.state_machine.current_state
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1074,10 +1255,10 @@ mod tests {
|
|||
}
|
||||
|
||||
// Test that memory state machine works correctly
|
||||
// The state machine should handle the two-phase addressing properly
|
||||
// The state machine should handle two-phase addressing properly
|
||||
rom.update();
|
||||
|
||||
// Verify that the ROM correctly read the test data
|
||||
// Verify that ROM correctly read test data
|
||||
assert_eq!(rom.read_rom(0).unwrap(), 0x12);
|
||||
assert_eq!(rom.read_rom(1).unwrap(), 0x34);
|
||||
assert_eq!(rom.read_rom(2).unwrap(), 0x56);
|
||||
|
|
@ -1221,7 +1402,7 @@ mod tests {
|
|||
|
||||
assert_eq!(rom.get_address_high_nibble(), Some(0x12));
|
||||
assert_eq!(rom.get_address_low_nibble(), Some(0x34));
|
||||
assert_eq!(rom.get_full_address_ready(), true);
|
||||
assert_eq!(rom.state_machine.is_full_address_ready(), true);
|
||||
|
||||
// Test clearing
|
||||
rom.set_address_high_nibble(None);
|
||||
|
|
@ -1230,7 +1411,7 @@ mod tests {
|
|||
|
||||
assert_eq!(rom.get_address_high_nibble(), None);
|
||||
assert_eq!(rom.get_address_low_nibble(), None);
|
||||
assert_eq!(rom.get_full_address_ready(), false);
|
||||
assert_eq!(rom.state_machine.is_full_address_ready(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -1298,16 +1479,10 @@ mod tests {
|
|||
|
||||
// Test I/O port selection during operation
|
||||
// This would be set during actual I/O operations
|
||||
// For testing, we can verify the field exists and can be modified
|
||||
// For testing, we can verify field exists and can be modified
|
||||
assert_eq!(rom.selected_io_port, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_io_direction_display() {
|
||||
assert_eq!(IoDirection::Input.to_string(), "Input");
|
||||
assert_eq!(IoDirection::Output.to_string(), "Output");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_intel4001_reset_io_behavior() {
|
||||
let mut rom = Intel4001::new("ResetIOTestROM".to_string());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue