rusty_emu/src/components/common/README.md
Jan-Peter von Hunnius 6a335a15d4 Fix Intel 4004 CPU instruction set and improve MCS-4 timing accuracy
Overhaul the Intel 4004 instruction set to match the real opcode map,
adding missing instructions (NOP, FIM, FIN, JIN, ISZ, KBP, DCL, WR0-3,
RD0-3) and refining two-byte instruction handling with proper M1/M2
data phases.  Rewrite the CPU fetch-execute cycle to correctly process
two-byte opcodes across separate clock cycles.

Improve Intel 4001 ROM timing with M1/M2 nibble phase tracking and an
A3 wait counter so the ROM drives data only when the CPU expects it.
Fix state-machine transition helpers in Intel 4002 and 4003 to capture
the old state before processing events, preventing incorrect exit/entry
callbacks.

Add non-propagating pin driver methods to prevent deadlocks caused by
bidirectional pin connections during signal propagation.

Add Fibonacci integration test and opcode fix plan document.
Update all README files to reflect the new instruction set.
2026-05-12 19:32:01 +02:00

15 KiB

Intel 400x Common Functionality

This module provides shared functionality for all Intel 400x series chips (4001, 4002, 4003, 4004) through a unified state machine architecture. It contains common traits, utilities, and behaviors that are used across multiple components.

Purpose

The Intel 400x series shares many common characteristics:

  • Two-phase clock operation (Φ1, Φ2)
  • 4-bit data bus with multiplexed address/data
  • 12-bit address space (8-bit addresses in two 4-bit cycles)
  • Common timing constraints and access patterns
  • Similar pin configurations and control signals
  • Unified state machine patterns for all operations

Rather than duplicating this code in each component, it's centralized here for maintainability and consistency.

Unified State Machine Architecture

Overview

The Intel 400x series now uses a unified state machine approach that replaces individual chip state machines with a single, extensible framework. This approach provides several benefits:

  1. Consistency: All components follow the same basic state transition patterns
  2. Maintainability: Common logic is centralized in the state machine engine
  3. Extensibility: New chip types can be added by implementing the Intel400xComponentBehavior trait
  4. Testability: Components can be tested independently with mock behaviors

Key Components

Intel400xComponentBehavior Trait

This trait defines the interface that all Intel 400x components must implement to work with the unified state machine architecture. It allows each component to provide its own specific behavior while still following the common state machine framework.

pub trait Intel400xComponentBehavior {
    fn get_component_type(&self) -> Intel400xComponentType;
    fn on_state_entry(&mut self, state: Intel400xUnifiedState);
    fn on_state_exit(&mut self, state: Intel400xUnifiedState);
    fn handle_component_event(&mut self, event: Intel400xEvent) -> bool;
    fn execute_state_logic(&mut self, state: Intel400xUnifiedState);
    fn responds_to_address(&self) -> bool;
}

Intel400xComponentType

Enumeration that identifies the specific type of Intel 400x component being modeled:

pub enum Intel400xComponentType {
    Intel4001, // ROM (Read-Only Memory)
    Intel4002, // RAM (Random Access Memory)
    Intel4003, // Shift Register
    Intel4004, // CPU (Central Processing Unit)
}

Intel400xUnifiedState

Enumeration representing all possible states for any Intel 400x component in the unified state machine:

pub enum Intel400xUnifiedState {
    // Common states (shared across all components)
    Idle,
    AddressPhase,
    WaitLatency,
    DriveData,
    
    // Component-specific states
    ReadData,      // Intel 4002 (RAM) specific
    WriteData,     // Intel 4002 (RAM) specific
    OutputPort,    // Intel 4002 (RAM) specific
    ShiftData,     // Intel 4003 (Shift Register) specific
    LoadData,      // Intel 4003 (Shift Register) specific
    FetchByte1,    // Intel 4004 (CPU) specific — fetching first byte
    FetchByte2,    // Intel 4004 (CPU) specific — fetching second byte of 2-byte instruction
    Execute,       // Intel 4004 (CPU) specific — executing decoded instruction
}

Intel400xEvent

Enumeration representing events that can trigger state transitions:

pub enum Intel400xEvent {
    ClockPhi1Rising,
    ClockPhi1Falling,
    ClockPhi2Rising,
    ClockPhi2Falling,
    SyncActive,
    SyncInactive,
    ChipSelect,
    ChipDeselect,
    ResetActive,
    ResetInactive,
    AddressComplete,
    LatencyElapsed,
    DataReady,
    OperationComplete,
}

Intel400xStateMachine

The core state machine engine that manages state transitions and coordinates with component behaviors:

pub struct Intel400xStateMachine {
    component_type: Intel400xComponentType,
    current_state: Intel400xUnifiedState,
    previous_state: Intel400xUnifiedState,
    address_high_nibble: Option<u8>,
    address_low_nibble: Option<u8>,
    address_latch_time: Option<Instant>,
    access_time: Duration,
}

Trait-Based Design

The common functionality is organized around several key traits:

Intel400xClockHandling    - Clock edge detection and timing
Intel400xDataBus         - Data bus read/write operations
Intel400xAddressHandling - Address latching and assembly
Intel400xControlPins     - Control signal reading
Intel400xResetHandling   - Reset signal handling
Intel400xTimingState     - Memory operation state management

Core Traits

Intel400xClockHandling

Handles the two-phase clock system used by all 400x components:

pub trait Intel400xClockHandling {
    fn get_base(&self) -> &BaseComponent;

    // Clock edge detection
    fn is_phi1_rising_edge(&self, prev_phi1: PinValue) -> bool;
    fn is_phi1_falling_edge(&self, prev_phi1: PinValue) -> bool;
    fn is_phi2_rising_edge(&self, prev_phi2: PinValue) -> bool;
    fn is_phi2_falling_edge(&self, prev_phi2: PinValue) -> bool;

    // Clock state management
    fn update_clock_states(&self, prev_phi1: &mut PinValue, prev_phi2: &mut PinValue);
}

Key Features:

  • Edge detection for both clock phases
  • Previous state tracking for transition detection
  • Thread-safe pin access

Intel400xDataBus

Manages the 4-bit multiplexed address/data bus:

pub trait Intel400xDataBus {
    fn get_base(&self) -> &BaseComponent;

    // Bus operations
    fn read_data_bus(&self) -> u8;           // Read 4-bit value from D0-D3
    fn write_data_bus(&self, data: u8);      // Drive 4-bit value on D0-D3
    fn tri_state_data_bus(&self);            // Set bus to high impedance
}

Key Features:

  • Safe pin access with error handling
  • Driver name tracking for debugging
  • Bus contention avoidance

Intel400xAddressHandling

Handles the two-phase addressing used by 400x components:

pub trait Intel400xAddressHandling {
    fn get_base(&self) -> &BaseComponent;

    // Address operations
    fn assemble_full_address(&self, high_nibble: Option<u8>, low_nibble: Option<u8>) -> Option<u16>;
    fn handle_address_latching(&self, nibble: u8, ...);  // Complex latching logic
    fn handle_latency_wait(&self, address_latch_time: &Option<Instant>, access_time: Duration) -> bool;
}

Key Features:

  • Two-phase address latching (high nibble, then low nibble)
  • Timing-aware address assembly
  • Access time validation

Intel400xControlPins

Reads control signals common to all 400x components:

pub trait Intel400xControlPins {
    fn get_base(&self) -> &BaseComponent;

    // Control signal reading
    fn read_sync_pin(&self) -> bool;    // SYNC signal state
    fn read_cm_rom_pin(&self) -> bool;   // CM-ROM chip select
    fn read_reset_pin(&self) -> bool;    // RESET signal state
}

Key Features:

  • Safe pin reading with fallback values
  • Consistent pin naming across components

Intel400xResetHandling

Provides reset functionality for all 400x components:

pub trait Intel400xResetHandling {
    fn get_base(&self) -> &BaseComponent;

    // Reset operations
    fn handle_reset(&mut self, reset_pin_name: &str) -> bool;
    fn perform_reset(&mut self);  // Component-specific reset logic
}

Key Features:

  • System-wide reset signal handling
  • Component-specific reset behaviors

Intel400xTimingState

Manages memory operation timing and state machines:

pub trait Intel400xTimingState {
    // State management
    fn get_timing_state(&self) -> MemoryState;
    fn set_timing_state(&mut self, state: MemoryState);

    // Address tracking
    fn get_address_latch_time(&self) -> Option<Instant>;
    fn set_address_latch_time(&mut self, time: Option<Instant>);

    // Address components
    fn get_full_address_ready(&self) -> bool;
    fn get_address_high_nibble(&self) -> Option<u8>;
    fn get_address_low_nibble(&self) -> Option<u8>;

    // Timing configuration
    fn get_access_time(&self) -> Duration;
}

Key Features:

  • Unified state machine interface
  • Timing constraint management
  • Address tracking and validation

State Machine Operation

State Transition Logic

The unified state machine operates by:

  1. Receiving events from the component or external sources
  2. Determining the next state based on current state and event
  3. Calling appropriate state entry/exit methods on the component behavior
  4. Executing state-specific logic through the component behavior

Common State Transitions

  1. Idle → AddressPhase: Triggered by chip select when component responds to address
  2. AddressPhase → WaitLatency: After both address nibbles are latched
  3. WaitLatency → Component-Specific State: After access time has elapsed
  4. Component-Specific State → Idle: When operation completes

Component-Specific Behaviors

  • Intel 4001 (ROM): Transitions to DriveData to provide read-only data
  • Intel 4002 (RAM): Transitions to ReadData, WriteData, or OutputPort based on operation
  • Intel 4003 (Shift Register): Ignores address transmission, goes directly to ShiftData
  • Intel 4004 (CPU): Uses FetchByte1, FetchByte2, and Execute states for instruction processing

Timing Constants

Standard Timing Values

impl TimingConstants {
    pub const DEFAULT_ACCESS_TIME: Duration = Duration::from_nanos(500); // 500ns default
    pub const FAST_ACCESS_TIME: Duration = Duration::from_nanos(200);    // 200ns for shift registers
    pub const ADDRESS_SETUP: Duration = Duration::from_nanos(100);       // Address setup time
    pub const DATA_VALID: Duration = Duration::from_nanos(200);          // Data valid delay
}

Usage in Components

Each component type uses appropriate timing constants:

  • ROM (4001): 500ns typical access time
  • RAM (4002): 500ns access time with refresh overhead
  • Shift Register (4003): 200ns fast access time
  • CPU (4004): Internal operation timing

Memory Operation States

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum MemoryState {
    Idle,         // No memory operation in progress
    AddressPhase, // Currently latching address nibbles
    WaitLatency,  // Address latched, waiting for access time
    DriveData,    // Latency elapsed, driving data on bus
}

Utility Functions

Pin Operations

pub mod utils {
    // Safe pin operations with error handling
    pub fn is_pin_high(pin: &Arc<Mutex<Pin>>) -> bool;
    pub fn is_pin_high_z(pin: &Arc<Mutex<Pin>>) -> bool;
    pub fn read_pin_safe(pin: &Arc<Mutex<Pin>>, default: PinValue) -> PinValue;
    pub fn set_pin_driver_safe(pin: &Arc<Mutex<Pin>>, driver_name: String, value: PinValue);

    // Component naming and identification
    pub fn create_driver_name(component_name: &str, suffix: &str) -> String;
}

Error Handling

All pin operations include proper error handling:

  • Lock failures: Return safe default values
  • Pin not found: Return appropriate error messages
  • Invalid states: Graceful degradation

Usage Examples

Implementing the Intel400xComponentBehavior Trait

struct MyComponent {
    // Component fields...
    state_machine: Intel400xStateMachine,
}

impl Intel400xComponentBehavior for MyComponent {
    fn get_component_type(&self) -> Intel400xComponentType {
        Intel400xComponentType::Intel4001 // Example type
    }

    fn on_state_entry(&mut self, state: Intel400xUnifiedState) {
        match state {
            Intel400xUnifiedState::AddressPhase => {
                // Prepare for address latching
            }
            Intel400xUnifiedState::DriveData => {
                // Prepare for data transfer
            }
            _ => {}
        }
    }

    // ... implement other methods
}

Using the Unified State Machine

// Create a state machine for an Intel 4001 ROM chip
let mut state_machine = Intel400xStateMachine::new(
    Intel400xComponentType::Intel4001,
    Duration::from_nanos(500)
);

// Process a chip select event
let new_state = process_event_direct(
    &mut state_machine,
    Intel400xEvent::ChipSelect
);

assert_eq!(new_state, Intel400xUnifiedState::AddressPhase);

Testing

Comprehensive Test Suite

The unified state machine is thoroughly tested with:

  1. Unit Tests: Test individual functions and algorithms
  2. Mock Tests: Test with controlled hardware inputs
  3. Property Tests: Verify behavioral properties
  4. Integration Tests: Test with real component implementations

Testable Areas

  • State machine transitions
  • Address latching algorithms
  • Component-specific behaviors
  • Clock edge detection accuracy
  • Data bus bit operations
  • Pin operation safety

Benefits of the Unified Approach

  1. Code Reuse: Common state transition logic is shared across all components
  2. Consistency: All components follow the same timing and state patterns
  3. Maintainability: Changes to common behavior only need to be made in one place
  4. Extensibility: New chip types can be added without modifying the core state machine
  5. Testability: The state machine can be tested independently of specific components

Hardware Accuracy

Timing Considerations

The unified state machine implements hardware-accurate timing where possible:

  • Clock Edge Detection: Proper Φ1/Φ2 edge detection
  • Address Latching: Two-phase address capture
  • Access Time Simulation: Configurable access latencies
  • Bus State Management: Proper drive/tri-state timing

Limitations

  • Rust Timing: Limited by Rust's timing resolution
  • Threading: Simulation runs in single thread vs. hardware parallelism
  • Signal Propagation: Simplified signal timing for simulation

Future Enhancements

Planned Improvements

  1. Enhanced Timing Simulation

    • More accurate sub-nanosecond timing
    • Better clock phase relationship modeling
    • Improved signal propagation delays
  2. Extended Component Support

    • Support for additional Intel 400x variants
    • Enhanced I/O device support
    • Better peripheral modeling
  3. Testing Improvements

    • More sophisticated mock scenarios
    • Better timing verification tools
    • Enhanced debugging capabilities

Extension Points

The trait-based design makes it easy to:

  • Add New Components: Implement the required traits
  • Enhance Timing: Modify timing constants and logic
  • Improve Testing: Add new mock scenarios and test utilities
  • Extend Functionality: Add new common behaviors as needed

Dependencies

The common functionality depends on:

  • Base Component System: For pin management and lifecycle
  • Pin Abstraction: For signal state management
  • Timing Utilities: For duration and instant handling
  • Error Handling: For robust operation

Performance

Optimization Features

  • Efficient Pin Access: Minimized lock contention
  • Fast State Transitions: Optimized state machine
  • Minimal Allocations: Reuse of common data structures
  • Lock-Free Algorithms: Where possible for performance

Memory Usage

  • Small Footprint: Minimal memory overhead per component
  • Shared Code: No duplication across components
  • Efficient State Storage: Compact state representation

This unified state machine architecture provides a solid foundation for all Intel 400x components while maintaining high performance and hardware accuracy.