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.
1092 lines
39 KiB
Rust
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);
|
|
}
|
|
}
|