rusty_emu/src/components/memory/intel_4002.rs
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

1092 lines
39 KiB
Rust

use parking_lot::Mutex;
use std::any::Any;
use std::collections::HashMap;
use std::sync::Arc;
use std::thread;
use std::time::{Duration, Instant};
use crate::component::{BaseComponent, Component, RunnableComponent};
use crate::components::common::intel_400x::{
process_event_direct, Intel400xAddressHandling, Intel400xClockHandling,
Intel400xComponentBehavior, Intel400xComponentType, Intel400xControlPins, Intel400xDataBus,
Intel400xEvent, Intel400xResetHandling, Intel400xStateMachine, Intel400xTimingState,
Intel400xUnifiedState, TimingState,
};
use crate::pin::{Pin, PinValue};
/// Intel 4002 - 320-byte RAM with I/O ports
/// Part of the MCS-4 family, designed to work with Intel 4004 CPU
/// Features 320 bytes of RAM, 4 I/O ports, and status character generation
///
/// Hardware Deviations:
/// - 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)
io_ports: [u8; 4], // 4 I/O ports (4 bits each)
status_character: u8, // 7-bit status character output
last_address: u16, // Last accessed memory address
access_time: Duration, // RAM access latency (500ns)
selected_register: Option<u8>, // Currently selected register (0-3)
selected_character: Option<u8>, // Currently selected character (0-79)
// 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
// Operation type for next cycle (determines ReadData vs WriteData vs OutputPort)
pending_operation: PendingOperation,
}
impl Intel400xClockHandling for Intel4002 {
fn get_base(&self) -> &BaseComponent {
&self.base
}
}
impl Intel400xDataBus for Intel4002 {
fn get_base(&self) -> &BaseComponent {
&self.base
}
}
impl Intel400xAddressHandling for Intel4002 {
fn get_base(&self) -> &BaseComponent {
&self.base
}
}
impl Intel400xControlPins for Intel4002 {
fn get_base(&self) -> &BaseComponent {
&self.base
}
}
impl Intel400xResetHandling for Intel4002 {
fn get_base(&self) -> &BaseComponent {
&self.base
}
fn perform_reset(&mut self) {
// Reset operations specific to Intel4002
// Note: This is called from handle_reset, so we don't need to check reset pin again
self.state_machine.reset();
self.tri_state_data_bus();
self.tri_state_status_pins();
// Clear RAM content
self.memory = [0u8; 320];
self.io_ports = [0u8; 4];
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;
}
}
impl Intel400xTimingState for Intel4002 {
fn get_timing_state(&self) -> TimingState {
// 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,
Intel400xUnifiedState::ReadData => TimingState::DriveData,
Intel400xUnifiedState::WriteData => TimingState::DriveData,
Intel400xUnifiedState::OutputPort => TimingState::DriveData,
// Handle all other states
_ => TimingState::Idle,
}
}
fn set_timing_state(&mut self, state: TimingState) {
// 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::ReadData, // Default to ReadData
};
// 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.state_machine.get_address_latch_time()
}
fn set_address_latch_time(&mut self, time: Option<Instant>) {
self.state_machine.set_address_latch_time(time);
}
fn get_full_address_ready(&self) -> bool {
self.state_machine.is_full_address_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.state_machine.get_address_high_nibble()
}
fn set_address_high_nibble(&mut self, nibble: Option<u8>) {
self.state_machine.set_address_high_nibble(nibble);
}
fn get_address_low_nibble(&self) -> Option<u8> {
self.state_machine.get_address_low_nibble()
}
fn set_address_low_nibble(&mut self, nibble: Option<u8>) {
self.state_machine.set_address_low_nibble(nibble);
}
fn get_access_time(&self) -> Duration {
self.state_machine.get_access_time()
}
}
impl Intel400xComponentBehavior for Intel4002 {
fn get_component_type(&self) -> Intel400xComponentType {
Intel400xComponentType::Intel4002
}
fn on_state_entry(&mut self, state: Intel400xUnifiedState) {
match state {
Intel400xUnifiedState::Idle => {
// Entry actions for Idle state
self.tri_state_data_bus();
self.tri_state_status_pins();
}
Intel400xUnifiedState::AddressPhase => {
// Entry actions for AddressPhase state
// 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
// Address is already latched, start access timer
}
Intel400xUnifiedState::ReadData => {
// Entry actions for ReadData state
// Data reading will happen in execute_state_logic
}
Intel400xUnifiedState::WriteData => {
// Entry actions for WriteData state
// Data writing will happen in execute_state_logic
}
Intel400xUnifiedState::OutputPort => {
// Entry actions for OutputPort state
// Status character output will happen in execute_state_logic
}
// Handle all other states
_ => {}
}
}
fn on_state_exit(&mut self, state: Intel400xUnifiedState) {
match state {
Intel400xUnifiedState::ReadData
| Intel400xUnifiedState::WriteData
| Intel400xUnifiedState::OutputPort => {
// Exit actions for data states
self.tri_state_data_bus();
self.tri_state_status_pins();
}
// 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();
self.tri_state_status_pins();
}
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 ReadData (default for RAM)
self.process_latency_elapsed();
}
}
Intel400xUnifiedState::ReadData => {
// Read data from RAM and drive on bus
self.handle_ram_read();
}
Intel400xUnifiedState::WriteData => {
// Write data from bus to RAM
self.handle_ram_write();
}
Intel400xUnifiedState::OutputPort => {
// Output status character on status pins
self.handle_status_output();
}
// Handle other states (not used by Intel 4002)
_ => {}
}
}
fn responds_to_address(&self) -> bool {
true // Intel 4002 responds to address transmission
}
}
impl Intel4002 {
/// Create a new Intel 4002 RAM with specified access time
/// Parameters: name - Component identifier, access_time_ns - Memory access time in nanoseconds
/// Returns: New Intel4002 instance with configurable access timing
pub fn new(name: String) -> Self {
Self::new_with_access_time(name, 500) // Default 500ns access time
}
/// Create a new Intel 4002 RAM with custom access time (for testing)
/// Parameters: name - Component identifier, access_time_ns - Memory access time in nanoseconds
/// Returns: New Intel4002 instance with configurable access timing
pub fn new_with_access_time(name: String, access_time_ns: u64) -> Self {
// Intel 4002 has 320 bytes of RAM
const RAM_SIZE: usize = 320;
// Intel 4002 pinout (per datasheet):
// - 4 data pins (D0-D3) for multiplexed address/data
// - 4 I/O ports (IO0-IO3)
// - 4 status pins (S0-S3) for status character output
// - Control pins: SYNC, RESET, CM, CI
// - Clock pins: Φ1, Φ2 (two-phase clock from 4004 CPU)
//
// Control pin behavior:
// - SYNC: Marks start of instruction cycle
// - RESET: Clears internal state
// - CM: Chip select (must be HIGH for RAM access)
// - CI: I/O select (distinguishes I/O vs memory when chip selected)
let pin_names = vec![
"D0", "D1", "D2", "D3", // Data/Address pins
"IO0", "IO1", "IO2", "IO3", // I/O ports
"S0", "S1", "S2", "S3", // Status character pins
"SYNC", // Sync signal
"CM", // CM-ROM: ROM/RAM Chip Select
"CI", // CM-RAM: RAM Chip Select (I/O vs ROM access)
"RESET", // Reset
"PHI1", // Clock phase 1
"PHI2", // Clock phase 2
];
let pins = BaseComponent::create_pin_map(&pin_names, &name);
Intel4002 {
base: BaseComponent::new(name, pins),
memory: [0u8; RAM_SIZE],
io_ports: [0u8; 4],
status_character: 0,
last_address: 0,
access_time: Duration::from_nanos(access_time_ns),
selected_register: None,
selected_character: None,
state_machine: Intel400xStateMachine::new(
Intel400xComponentType::Intel4002,
Duration::from_nanos(access_time_ns),
),
prev_phi1: PinValue::Low,
prev_phi2: PinValue::Low,
pending_operation: PendingOperation::Read, // Default to read
}
}
/// 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 current memory access time
/// Returns: Access time in nanoseconds
pub fn get_access_time(&self) -> u64 {
self.access_time.as_nanos() as u64
}
/// Read a byte from RAM at specified address
/// Parameters: address - 8-bit address (0-319)
/// Returns: Some(data) if address valid, None if out of bounds
pub fn read_ram(&self, address: u8) -> Option<u8> {
if (address as usize) < self.memory.len() {
Some(self.memory[address as usize])
} else {
None
}
}
/// Write a byte to RAM at specified address
/// Parameters: address - 8-bit address (0-319), data - Byte to write
/// Returns: Ok(()) on success, Err(String) on failure
pub fn write_ram(&mut self, address: u8, data: u8) -> Result<(), String> {
if (address as usize) < self.memory.len() {
self.memory[address as usize] = data;
Ok(())
} else {
Err(format!("Address 0x{:02X} out of bounds", address))
}
}
/// Write to a specific I/O port
/// Parameters: port - I/O port number (0-3), data - 4-bit data to write
/// Returns: Ok(()) on success, Err(String) on failure
pub fn write_io_port(&mut self, port: usize, data: u8) -> Result<(), String> {
if port < 4 {
self.io_ports[port] = data & 0x0F;
Ok(())
} else {
Err("I/O port number out of range (0-3)".to_string())
}
}
/// Get current status character
/// Returns: 7-bit status character
/// Tri-state data bus pins
fn tri_state_data_bus(&self) {
for i in 0..4 {
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
let mut pin_guard = pin.lock();
// 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);
}
}
}
/// Tri-state status character pins
fn tri_state_status_pins(&self) {
for i in 0..4 {
if let Ok(pin) = self.base.get_pin(&format!("S{}", i)) {
let mut pin_guard = pin.lock();
pin_guard.set_driver(Some(self.base.name()), PinValue::HighZ);
}
}
}
/// Handle Φ1 rising edge - Address and control phase
/// Hardware: Φ1 high = CPU drives bus with address/control information
/// Memory operations start when SYNC goes high during Φ1 rising edge
/// Focus: Address latching, reset, I/O operations
fn handle_phi1_rising(&mut self) {
// Handle system reset first (highest priority)
self.handle_reset("RESET");
// Check for memory operation start on Φ1 rising edge with SYNC high
// Hardware: Memory operations start when SYNC goes high during Φ1
// Note: SYNC marks instruction fetch start, but exact logic involves CPU cycle state.
// RAM access: CM=1 (chip_select), CI=1 (io_select)
let sync = self.read_sync_pin();
let chip_select = self.read_cm_rom_pin();
let io_select = self.read_ci_pin();
if sync && chip_select && io_select {
// Start memory address phase on Φ1 rising edge (RAM access)
// Only start a new memory operation if we're currently idle
if self.state_machine.get_state() == Intel400xUnifiedState::Idle {
self.process_chip_select();
}
}
}
/// 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) {
// 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) {
// 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
/// Hardware: Φ2 low = End of data driving phase, time to tri-state bus
/// 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) {
match self.state_machine.get_state() {
Intel400xUnifiedState::ReadData
| Intel400xUnifiedState::WriteData
| Intel400xUnifiedState::OutputPort => {
// End of data phase - tri-state bus and return to idle
self.tri_state_data_bus();
self.tri_state_status_pins();
self.process_operation_complete();
}
Intel400xUnifiedState::Idle
| Intel400xUnifiedState::AddressPhase
| Intel400xUnifiedState::WaitLatency => {
// For other states, just ensure bus is tri-stated
self.tri_state_data_bus();
self.tri_state_status_pins();
}
// Handle all other states
_ => {
// Default behavior for unhandled states
self.tri_state_data_bus();
self.tri_state_status_pins();
}
}
}
/// Handle address nibble latching during address phase
/// Hardware: CPU provides 8-bit address as two 4-bit nibbles
fn handle_address_latching(&mut self) {
// Use common address latching functionality
let nibble = self.read_data_bus() & 0x0F; // Only 4 bits are valid for address nibbles
if self.state_machine.handle_address_nibble(nibble) {
// Address is complete, transition to latency wait
self.process_address_complete();
}
}
/// Handle RAM read operation
/// Hardware: RAM drives data bus with requested data
fn handle_ram_read(&mut self) {
if self.state_machine.is_full_address_ready() {
let address = self.state_machine.get_full_address().unwrap_or(0) as u8;
if let Some(data) = self.read_ram(address) {
self.write_data_bus(data);
self.last_address = address as u16;
} else {
self.tri_state_data_bus();
}
} else {
self.tri_state_data_bus();
}
}
/// Handle RAM write operation
/// Hardware: RAM reads data from bus and stores at address
fn handle_ram_write(&mut self) {
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();
if self.write_ram(address, data).is_ok() {
self.last_address = address as u16;
}
}
}
/// 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) {
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) {
// 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
};
// 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 = target_state;
self.on_state_entry(target_state);
}
}
/// Helper function to process chip select event
fn process_chip_select(&mut self) {
let old_state = self.state_machine.get_state();
let event = Intel400xEvent::ChipSelect;
let new_state = process_event_direct(&mut self.state_machine, event);
if new_state != old_state {
self.on_state_exit(old_state);
self.on_state_entry(new_state);
}
}
/// Helper function to process operation complete event
fn process_operation_complete(&mut self) {
let old_state = self.state_machine.get_state();
let event = Intel400xEvent::OperationComplete;
let new_state = process_event_direct(&mut self.state_machine, event);
if new_state != old_state {
self.on_state_exit(old_state);
self.on_state_entry(new_state);
}
}
/// Helper function to process address complete event
fn process_address_complete(&mut self) {
let old_state = self.state_machine.get_state();
let event = Intel400xEvent::AddressComplete;
let new_state = process_event_direct(&mut self.state_machine, event);
if new_state != old_state {
self.on_state_exit(old_state);
self.on_state_entry(new_state);
}
}
}
impl Component for Intel4002 {
fn name(&self) -> String {
self.base.name()
}
fn pins(&self) -> HashMap<String, Arc<Mutex<Pin>>> {
self.base.pins()
}
fn get_pin(&self, name: &str) -> Result<Arc<Mutex<Pin>>, String> {
self.base.get_pin(name)
}
/// Main update cycle - handles clock edge detection and operation dispatch
/// Hardware: Responds to Φ1 and Φ2 clock edges from CPU
fn update(&mut self) {
if !self.is_running() {
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);
let phi2_rising = self.is_phi2_rising_edge(self.prev_phi2);
let phi2_falling = self.is_phi2_falling_edge(self.prev_phi2);
// Update clock states for next edge detection
let (phi1, phi2) = self.read_clock_pins();
self.prev_phi1 = phi1;
self.prev_phi2 = phi2;
// 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 {
self.handle_phi1_rising();
}
if phi1_falling {
self.handle_phi1_falling();
}
if phi2_rising {
self.handle_phi2_rising();
}
if phi2_falling {
self.handle_phi2_falling();
}
// 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)
/// Hardware: Simulates continuous operation with clock edge detection
fn run(&mut self) {
self.base.set_running(true);
// Initialize clock states for edge detection
let (phi1, phi2) = self.read_clock_pins();
self.prev_phi1 = phi1;
self.prev_phi2 = phi2;
while self.is_running() {
self.update();
// Small delay to prevent busy waiting when no clock is present
thread::sleep(Duration::from_micros(1));
}
}
/// Stop component and tri-state all outputs
/// Hardware: Component enters low-power state, all pins high-impedance
fn stop(&mut self) {
self.base.set_running(false);
// Tri-state all outputs when stopped
self.tri_state_data_bus();
self.tri_state_status_pins();
}
fn is_running(&self) -> bool {
self.base.is_running()
}
fn set_running(&mut self, running: bool) {
self.base.set_running(running);
}
fn as_any(&self) -> &dyn Any {
self
}
fn as_any_mut(&mut self) -> &mut dyn Any {
self
}
}
impl RunnableComponent for Intel4002 {}
// Intel 4002 specific methods
impl Intel4002 {
/// Get RAM size in bytes
/// Returns: Total number of bytes in RAM (320 for 4002)
pub fn get_ram_size(&self) -> usize {
self.memory.len()
}
/// Read a byte from RAM at specified address
/// Parameters: address - 8-bit address (0-319)
/// Returns: Some(data) if address valid, None if out of bounds
pub fn read_ram_direct(&self, address: u8) -> Option<u8> {
if (address as usize) < self.memory.len() {
Some(self.memory[address as usize])
} else {
None
}
}
/// Write a byte to RAM at specified address
/// Parameters: address - 8-bit address (0-319), data - Byte to write
/// Returns: Ok(()) on success, Err(String) on failure
pub fn write_ram_direct(&mut self, address: u8, data: u8) -> Result<(), String> {
if (address as usize) < self.memory.len() {
self.memory[address as usize] = data;
self.last_address = address as u16; // Update last address tracking
Ok(())
} else {
Err(format!("Address 0x{:02X} out of bounds", address))
}
}
/// Get I/O port value
/// Parameters: port - I/O port number (0-3)
/// Returns: Some(4-bit value) if port valid, None if invalid
pub fn get_io_port(&self, port: usize) -> Option<u8> {
if port < 4 {
Some(self.io_ports[port])
} else {
None
}
}
/// Set I/O port value (for testing/debugging)
/// Parameters: port - I/O port number (0-3), data - 4-bit data to set
/// Returns: Ok(()) on success, Err(String) on failure
pub fn set_io_port(&mut self, port: usize, data: u8) -> Result<(), String> {
if port < 4 {
self.io_ports[port] = data & 0x0F;
Ok(())
} else {
Err("I/O port number out of range (0-3)".to_string())
}
}
/// Get current status character
/// Returns: 7-bit status character
pub fn get_status_character(&self) -> u8 {
self.status_character
}
/// Set status character (for testing/debugging)
/// Parameters: character - 7-bit status character
pub fn set_status_character(&mut self, character: u8) {
self.status_character = character & 0x7F; // Only 7 bits valid
}
/// Get last accessed address
/// Returns: Last address accessed by RAM
pub fn get_last_address(&self) -> u16 {
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) {
println!(
"{} - State: {:?}, High: {:?}, Low: {:?}, Address: 0x{:x}, Ready: {}, Latch time: {:?}",
test_name,
self.state_machine.get_state(),
self.state_machine.get_address_high_nibble(),
self.state_machine.get_address_low_nibble(),
self.last_address,
self.state_machine.is_full_address_ready(),
self.state_machine.get_address_latch_time()
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_intel4002_creation() {
let ram = Intel4002::new("RAM_4002".to_string());
assert_eq!(ram.name(), "RAM_4002");
assert_eq!(ram.get_ram_size(), 320);
assert_eq!(ram.get_access_time(), 500); // Default 500ns
assert!(!ram.is_running());
}
#[test]
fn test_intel4002_ram_operations() {
let mut ram = Intel4002::new("RAM_4002".to_string());
// Test RAM read/write operations
assert!(ram.write_ram_direct(0x00, 0x12).is_ok());
assert!(ram.write_ram_direct(0x01, 0x34).is_ok());
assert!(ram.write_ram_direct(0x02, 0x56).is_ok());
assert!(ram.write_ram_direct(0x03, 0x78).is_ok());
assert_eq!(ram.read_ram_direct(0x00).unwrap(), 0x12);
assert_eq!(ram.read_ram_direct(0x01).unwrap(), 0x34);
assert_eq!(ram.read_ram_direct(0x02).unwrap(), 0x56);
assert_eq!(ram.read_ram_direct(0x03).unwrap(), 0x78);
}
#[test]
fn test_intel4002_io_ports() {
let mut ram = Intel4002::new("RAM_4002".to_string());
// Test I/O port operations
assert!(ram.set_io_port(0, 0x05).is_ok());
assert!(ram.set_io_port(1, 0x0A).is_ok());
assert!(ram.set_io_port(2, 0x0F).is_ok());
assert!(ram.set_io_port(3, 0x00).is_ok());
assert_eq!(ram.get_io_port(0).unwrap(), 0x05);
assert_eq!(ram.get_io_port(1).unwrap(), 0x0A);
assert_eq!(ram.get_io_port(2).unwrap(), 0x0F);
assert_eq!(ram.get_io_port(3).unwrap(), 0x00);
// Test invalid port access
assert!(ram.set_io_port(4, 0x0F).is_err());
assert!(ram.get_io_port(4).is_none());
}
#[test]
fn test_intel4002_status_character() {
let mut ram = Intel4002::new("RAM_4002".to_string());
// Test status character operations
ram.set_status_character(0x5A); // Binary 1011010
assert_eq!(ram.get_status_character(), 0x5A);
// Test status character bit masking
ram.set_status_character(0xFF); // Should be masked to 0x7F
assert_eq!(ram.get_status_character(), 0x7F);
}
#[test]
fn test_intel4002_state_transitions() {
let mut ram = Intel4002::new_with_access_time("StateTestRAM".to_string(), 1);
// Test all state transitions
let states = vec![
TimingState::Idle,
TimingState::AddressPhase,
TimingState::WaitLatency,
TimingState::DriveData,
];
for &state in &states {
ram.set_timing_state(state);
assert_eq!(ram.get_timing_state(), state);
}
// Test return to idle
ram.set_timing_state(TimingState::Idle);
assert_eq!(ram.get_timing_state(), TimingState::Idle);
}
#[test]
fn test_intel4002_address_handling() {
let mut ram = Intel4002::new_with_access_time("AddressTestRAM".to_string(), 1);
// Test address nibble handling
ram.set_address_high_nibble(Some(0x12));
ram.set_address_low_nibble(Some(0x34));
ram.set_full_address_ready(true);
assert_eq!(ram.get_address_high_nibble(), Some(0x12));
assert_eq!(ram.get_address_low_nibble(), Some(0x34));
assert_eq!(ram.state_machine.is_full_address_ready(), true);
// Test clearing
ram.set_address_high_nibble(None);
ram.set_address_low_nibble(None);
ram.set_full_address_ready(false);
assert_eq!(ram.get_address_high_nibble(), None);
assert_eq!(ram.get_address_low_nibble(), None);
assert_eq!(ram.state_machine.is_full_address_ready(), false);
}
#[test]
fn test_intel4002_error_conditions() {
let mut ram = Intel4002::new("ErrorTestRAM".to_string());
// Test invalid memory access
assert_eq!(ram.read_ram_direct(0xFF), Some(0x00)); // Default value for unmapped memory
// Test invalid data loading
assert!(ram.write_ram_direct(0x00, 0x12).is_ok()); // Valid
assert!(ram.write_ram_direct(0xFF, 0x34).is_ok()); // Valid - 0xFF is within 320-byte RAM
// Note: Since write_ram_direct takes u8 for address, we can't test addresses > 0xFF
// The actual implementation should handle this correctly for the full 320-byte range
}
#[test]
fn test_intel4002_io_port_operations() {
let mut ram = Intel4002::new("IOTestRAM".to_string());
// Test I/O port initialization
assert_eq!(ram.get_io_port(0).unwrap(), 0);
assert_eq!(ram.get_io_port(1).unwrap(), 0);
assert_eq!(ram.get_io_port(2).unwrap(), 0);
assert_eq!(ram.get_io_port(3).unwrap(), 0);
// Test setting I/O port values
assert!(ram.set_io_port(0, 0x05).is_ok());
assert_eq!(ram.get_io_port(0).unwrap(), 0x05);
// Test invalid port access
assert!(ram.set_io_port(4, 0x0F).is_err());
assert!(ram.get_io_port(4).is_none());
}
#[test]
fn test_intel4002_status_character_operations() {
let mut ram = Intel4002::new("StatusTestRAM".to_string());
// Test status character initialization
assert_eq!(ram.get_status_character(), 0);
// Test setting status character
ram.set_status_character(0x5A);
assert_eq!(ram.get_status_character(), 0x5A);
// Test status character bit masking
ram.set_status_character(0xFF); // Should be masked to 0x7F
assert_eq!(ram.get_status_character(), 0x7F);
}
#[test]
fn test_intel4002_reset_behavior() {
let mut ram = Intel4002::new("ResetTestRAM".to_string());
// Set up some state first
ram.set_io_port(0, 0x05).unwrap();
ram.set_io_port(1, 0x0A).unwrap();
ram.set_status_character(0x5A);
// Verify state is set
assert_eq!(ram.get_io_port(0).unwrap(), 0x05);
assert_eq!(ram.get_io_port(1).unwrap(), 0x0A);
assert_eq!(ram.get_status_character(), 0x5A);
// Perform reset
ram.perform_reset();
// Verify all state is cleared
assert_eq!(ram.get_io_port(0).unwrap(), 0);
assert_eq!(ram.get_io_port(1).unwrap(), 0);
assert_eq!(ram.get_status_character(), 0);
// Verify RAM is cleared
for i in 0..320 {
assert_eq!(ram.read_ram_direct(i as u8).unwrap(), 0x00);
}
}
#[test]
fn test_intel4002_last_address_tracking() {
let mut ram = Intel4002::new("AddressTrackTestRAM".to_string());
// Initially should be 0
assert_eq!(ram.get_last_address(), 0);
// Perform some operations
ram.write_ram_direct(0x10, 0x12).unwrap();
assert_eq!(ram.get_last_address(), 0x10);
ram.write_ram_direct(0x20, 0x34).unwrap();
assert_eq!(ram.get_last_address(), 0x20);
ram.write_ram_direct(0x30, 0x56).unwrap();
assert_eq!(ram.get_last_address(), 0x30);
}
#[test]
fn test_intel4002_debug_function() {
let mut ram = Intel4002::new("DebugTestRAM".to_string());
// Test debug state transitions
ram.set_address_high_nibble(Some(0x12));
ram.set_address_low_nibble(Some(0x34));
ram.set_full_address_ready(true);
// Call debug function
ram.debug_state_transitions("TestDebug");
// Should not panic or crash
assert_eq!(ram.get_address_high_nibble(), Some(0x12));
assert_eq!(ram.get_address_low_nibble(), Some(0x34));
assert_eq!(ram.state_machine.is_full_address_ready(), true);
}
}