rusty_emu/src/components/memory/intel_4002.rs

1375 lines
49 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
use crate::component::{BaseComponent, Component, RunnableComponent};
use crate::components::common::intel_400x::{
Intel400xAddressHandling, Intel400xClockHandling, Intel400xControlPins, Intel400xDataBus,
Intel400xResetHandling, Intel400xTimingState, RamState, TimingState,
};
use crate::pin::{Pin, PinValue};
/// Intel 4002 - 320-bit RAM (80 nibbles × 4 bits) with integrated output ports
/// Part of the MCS-4 family, designed to work with Intel 4004 CPU
/// Features 80 nibbles of RAM organized in 4 banks of 20 nibbles each,
/// plus 4 status characters and 4 output ports
///
/// Hardware Architecture:
/// - 4 banks × 20 registers × 4 bits = 80 nibbles total
/// - Status characters are separate 4-bit latches (not part of RAM)
/// - 4 output ports, each 4 bits wide
/// - Complex addressing via SRC/WRM/RDM instructions from CPU
///
/// Hardware Deviations:
/// - Simplified timing model for unit testing (may need refinement for CPU integration)
/// - Output port behavior matches 4001 I/O latch persistence
/// - Bank selection and status character handling follows Intel MCS-4 architecture
pub struct Intel4002 {
base: BaseComponent,
variant: RamVariant, // RAM variant (4002-1 or 4002-2)
memory: [u8; 80], // 80 nibbles of RAM (320 bits total) - 4 banks × 20 nibbles
last_address: u8, // Last accessed memory address
access_time: Duration, // RAM access latency (500ns typical)
address_latch_time: Option<Instant>, // Timestamp when address was latched
output_ports: [u8; 4], // 4 output ports (4 bits each) - TODO: Make [[u8; 4]; 4] for 4-bit ports
input_latch: u8, // Input data latch for I/O operations
status_characters: [u8; 4], // 4 separate status character latches (4 bits each)
bank_select: u8, // RAM bank selection (2 bits)
// Clock edge detection (same as 4001)
prev_phi1: PinValue, // Previous Φ1 clock state for edge detection
prev_phi2: PinValue, // Previous Φ2 clock state for edge detection
// Two-phase addressing for 8-bit address (same as 4001)
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
// RAM operation state machine
ram_state: RamState, // Current state of RAM operation
// Data latching for RAM operations
data_latch: Option<u8>, // Latched data for write operations
// Instruction cycle tracking
instruction_phase: bool, // Whether we're in instruction phase
current_instruction: u8, // Current instruction being processed
// Cycle counting for debug output
cycle_count: u64, // Total number of clock cycles executed
}
/// Intel 4002 RAM variants
/// - 4002-1: Standard RAM variant
/// - 4002-2: RAM variant with different configuration
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum RamVariant {
Type1, // 4002-1
Type2, // 4002-2
}
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.set_timing_state(TimingState::Idle)
}
}
impl Intel400xTimingState for Intel4002 {
fn get_timing_state(&self) -> TimingState {
self.ram_state.into()
}
fn set_timing_state(&mut self, state: TimingState) {
self.ram_state = state.into();
}
fn get_address_latch_time(&self) -> Option<Instant> {
self.address_latch_time
}
fn set_address_latch_time(&mut self, time: Option<Instant>) {
self.address_latch_time = time;
}
fn get_full_address_ready(&self) -> bool {
self.full_address_ready
}
fn set_full_address_ready(&mut self, ready: bool) {
self.full_address_ready = ready;
}
fn get_address_high_nibble(&self) -> Option<u8> {
self.address_high_nibble
}
fn set_address_high_nibble(&mut self, nibble: Option<u8>) {
self.address_high_nibble = nibble;
}
fn get_address_low_nibble(&self) -> Option<u8> {
self.address_low_nibble
}
fn set_address_low_nibble(&mut self, nibble: Option<u8>) {
self.address_low_nibble = nibble;
}
fn get_access_time(&self) -> Duration {
self.access_time
}
}
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_variant_and_access_time(name, RamVariant::Type1, 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 {
Self::new_with_variant_and_access_time(name, RamVariant::Type1, access_time_ns)
}
/// Create a new Intel 4002 RAM with specified variant and access time
/// Parameters: name - Component identifier, variant - RAM variant (Type1 or Type2), access_time_ns - Memory access time in nanoseconds
/// Returns: New Intel4002 instance with configurable variant and access timing
pub fn new_with_variant_and_access_time(
name: String,
variant: RamVariant,
access_time_ns: u64,
) -> Self {
// Intel 4002 pinout (based on MCS-4 architecture):
// - 4 data pins (D0-D3) for multiplexed address/data
// - 4 output port pins (O0-O3)
// - Control pins: SYNC, CM, P0, RESET
// - Clock pins: Φ1, Φ2 (two-phase clock from 4004 CPU)
//
// Control pin behavior:
// - SYNC: Marks start of instruction cycle
// - CM: ROM chip select (must be HIGH for ROM access)
// - P0: RAM chip select (must be HIGH for RAM access)
// - RESET: Clears internal state
let pin_names = vec![
"D0", "D1", "D2", "D3", // Data/Address pins
"O0", "O1", "O2", "O3", // Output port pins
"SYNC", // Sync signal
"CM", // ROM Chip Select
"P0", // RAM Chip Select
"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),
variant,
memory: [0u8; 80], // 80 nibbles = 4 banks × 20 nibbles each
last_address: 0,
access_time: Duration::from_nanos(access_time_ns),
address_latch_time: None,
output_ports: [0u8; 4],
input_latch: 0,
status_characters: [0u8; 4], // 4 separate status character latches
bank_select: 0,
prev_phi1: PinValue::Low,
prev_phi2: PinValue::Low,
address_high_nibble: None,
address_low_nibble: None,
full_address_ready: false,
ram_state: RamState::Idle,
data_latch: None,
instruction_phase: false,
current_instruction: 0,
cycle_count: 0,
}
}
/// Set the 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);
}
/// Get the current memory access time
/// Returns: Access time in nanoseconds
pub fn get_access_time(&self) -> u64 {
self.access_time.as_nanos() as u64
}
/// Initialize RAM with data
/// Parameters: data - Binary data to load (max 80 nibbles)
/// Returns: Ok(()) on success, Err(String) on failure
pub fn initialize_ram(&mut self, data: &[u8]) -> Result<(), String> {
if data.len() > 80 {
return Err("Data exceeds RAM capacity (80 nibbles)".to_string());
}
for (i, &byte) in data.iter().enumerate() {
self.memory[i] = byte & 0x0F; // Store only lower 4 bits
}
Ok(())
}
/// Read the 4-bit data bus from D0-D3 pins
/// Returns: 4-bit value from data bus pins
fn read_data_bus(&self) -> u8 {
let mut data = 0;
for i in 0..4 {
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
if let Ok(pin_guard) = pin.lock() {
if pin_guard.read() == PinValue::High {
data |= 1 << i;
}
}
}
}
data & 0x0F // Return only lower 4 bits
}
/// Drive the 4-bit data bus with the specified value
/// Parameters: data - 4-bit value to drive on D0-D3 pins
fn write_data_bus(&self, data: u8) {
let nibble = data & 0x0F; // Only lower 4 bits
for i in 0..4 {
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
if let Ok(mut pin_guard) = pin.lock() {
let bit_value = (nibble >> i) & 1;
let pin_value = if bit_value == 1 {
PinValue::High
} else {
PinValue::Low
};
// Use unique driver name to avoid conflicts with other components
pin_guard.set_driver(Some(format!("{}_DATA", self.base.name())), pin_value);
}
}
}
}
/// Update output port pins based on current output port values
/// Hardware: Output ports are driven continuously until changed or reset
fn update_output_ports(&self) {
for port in 0..4 {
if let Ok(pin) = self.base.get_pin(&format!("O{}", port)) {
if let Ok(mut pin_guard) = pin.lock() {
// Each output port drives its corresponding pin
let bit_value = (self.output_ports[port] >> 0) & 1;
let pin_value = if bit_value == 1 {
PinValue::High
} else {
PinValue::Low
};
// Use unique driver name for output ports
pin_guard.set_driver(Some(format!("{}_OUTPUT", self.base.name())), pin_value);
}
}
}
}
/// Set data bus to high-impedance state to avoid bus contention
/// CRITICAL: Must be called whenever RAM is not actively driving valid data
fn tri_state_data_bus(&self) {
for i in 0..4 {
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
if let Ok(mut pin_guard) = pin.lock() {
pin_guard
.set_driver(Some(format!("{}_DATA", self.base.name())), PinValue::HighZ);
}
}
}
}
/// Tri-state output port pins
/// Hardware: Output ports remain driven until explicitly changed
fn tri_state_output_ports(&self) {
for port in 0..4 {
if let Ok(pin) = self.base.get_pin(&format!("O{}", port)) {
if let Ok(mut pin_guard) = pin.lock() {
pin_guard.set_driver(
Some(format!("{}_OUTPUT", self.base.name())),
PinValue::HighZ,
);
}
}
}
}
/// Read the two-phase clock pins from CPU
/// Returns: (PHI1_value, PHI2_value)
/// Hardware: Intel 4004 provides two-phase clock for synchronization
fn read_clock_pins(&self) -> (PinValue, PinValue) {
let phi1 = if let Ok(pin) = self.base.get_pin("PHI1") {
if let Ok(pin_guard) = pin.lock() {
pin_guard.read()
} else {
PinValue::Low
}
} else {
PinValue::Low
};
let phi2 = if let Ok(pin) = self.base.get_pin("PHI2") {
if let Ok(pin_guard) = pin.lock() {
pin_guard.read()
} else {
PinValue::Low
}
} else {
PinValue::Low
};
(phi1, phi2)
}
/// Read all control pins from CPU
/// Returns: (sync, cm, p0, reset)
/// Hardware: Control pins determine operation type and chip state
fn read_control_pins(&self) -> (bool, bool, bool, bool) {
let sync = if let Ok(pin) = self.base.get_pin("SYNC") {
if let Ok(pin_guard) = pin.lock() {
pin_guard.read() == PinValue::High
} else {
false
}
} else {
false
};
let cm = if let Ok(pin) = self.base.get_pin("CM") {
if let Ok(pin_guard) = pin.lock() {
pin_guard.read() == PinValue::High
} else {
false
}
} else {
false
};
let p0 = if let Ok(pin) = self.base.get_pin("P0") {
if let Ok(pin_guard) = pin.lock() {
pin_guard.read() == PinValue::High
} else {
false
}
} else {
false
};
let reset = if let Ok(pin) = self.base.get_pin("RESET") {
if let Ok(pin_guard) = pin.lock() {
pin_guard.read() == PinValue::High
} else {
false
}
} else {
false
};
(sync, cm, p0, reset)
}
/// Handle system reset signal
/// Hardware: RESET pin clears all internal state and tri-states outputs
fn handle_reset(&mut self) {
if Intel400xResetHandling::handle_reset(self, "RESET") {
// Hardware reset - clear all registers specific to Intel4002
self.memory = [0u8; 80]; // Clear 80 nibbles
self.output_ports = [0u8; 4];
self.input_latch = 0;
self.status_characters = [0u8; 4]; // Clear 4 status character latches
self.bank_select = 0;
// Reset all state machines using common functionality
self.set_timing_state(TimingState::Idle);
self.set_address_latch_time(None);
self.set_address_high_nibble(None);
self.set_address_low_nibble(None);
self.set_full_address_ready(false); // Reset address ready on reset
self.data_latch = None;
self.instruction_phase = false;
self.current_instruction = 0;
self.cycle_count = 0;
// Tri-state all outputs
self.tri_state_data_bus();
self.tri_state_output_ports();
}
}
/// Assemble complete 8-bit address from high and low nibbles
/// Hardware: Intel 4004 provides address in two 4-bit phases
/// Format: (high_nibble << 4) | low_nibble
fn assemble_full_address(&mut self) {
if let (Some(high), Some(low)) = (self.address_high_nibble, self.address_low_nibble) {
// Assemble 8-bit address: (high << 4) | low
self.last_address = (high << 4) | low;
self.full_address_ready = true;
self.address_latch_time = Some(Instant::now());
println!("DEBUG: [{}] Addr Assembled | High: 0x{:X} | Low: 0x{:X} | Full: 0x{:02X} | Ready: {} | Cycles: {}",
self.base.name(), high, low, self.last_address, self.full_address_ready, self.get_cycle_count());
// Clear nibble storage for next address
self.address_high_nibble = None;
self.address_low_nibble = None;
}
}
/// Handle output port operations
/// Hardware: Output ports are separate from RAM, continuously driven
fn handle_output_port_operation(&mut self, port: usize, data: u8) {
if port < 4 {
self.output_ports[port] = data & 0x0F;
self.update_output_ports();
}
}
/// Handle input latch operations
/// Hardware: RDM (Read Data Memory) instructions read from input pins
fn handle_input_latch_operation(&mut self) {
// In real hardware, this would read from external input pins
// For emulation, we use the input latch
self.write_data_bus(self.input_latch);
}
/// Handle Φ1 rising edge - Address and control phase
/// Hardware: Φ1 high = CPU drives bus with address/control information
/// RAM operations start when SYNC goes high during Φ1 rising edge
fn handle_phi1_rising(&mut self) {
// Handle system reset first (highest priority)
self.handle_reset();
// Check for RAM operation start on Φ1 rising edge with SYNC high
let (sync, _cm, p0, _) = self.read_control_pins();
if sync && p0 && self.ram_state == RamState::Idle {
// Start RAM address phase on Φ1 rising edge (only when idle)
self.start_ram_address_phase();
}
// Handle RAM address phase operations during Φ1
self.handle_ram_address_operations();
}
/// Handle Φ1 falling edge - End of address phase
fn handle_phi1_falling(&mut self) {
// Currently no specific operations needed on Φ1 falling
}
/// Handle Φ2 rising edge - Data phase
fn handle_phi2_rising(&mut self) {
self.handle_ram_data_operations();
}
/// Handle Φ2 falling edge - End of data phase
fn handle_phi2_falling(&mut self) {
self.handle_ram_cleanup_operations();
}
/// Handle RAM address-related operations during Φ1
fn handle_ram_address_operations(&mut self) {
match self.ram_state {
RamState::Idle => {
self.tri_state_data_bus();
}
RamState::AddressPhase => {
self.handle_address_latching();
}
RamState::WaitLatency => {
self.handle_latency_wait();
}
RamState::ReadData | RamState::WriteData | RamState::OutputPort => {
self.tri_state_data_bus();
}
}
}
/// Handle RAM data-related operations during Φ2
fn handle_ram_data_operations(&mut self) {
match self.ram_state {
RamState::Idle => {
self.tri_state_data_bus();
}
RamState::AddressPhase => {
self.tri_state_data_bus();
}
RamState::WaitLatency => {
self.handle_latency_wait();
if self.ram_state == RamState::ReadData || self.ram_state == RamState::WriteData {
self.handle_data_operations();
}
}
RamState::ReadData => {
self.handle_data_operations();
}
RamState::WriteData => {
self.handle_data_operations();
}
RamState::OutputPort => {
self.handle_output_port_state();
}
}
}
/// Handle RAM cleanup operations on Φ2 falling edge
fn handle_ram_cleanup_operations(&mut self) {
match self.ram_state {
RamState::ReadData | RamState::WriteData | RamState::OutputPort => {
self.tri_state_data_bus();
self.return_to_idle();
}
RamState::Idle | RamState::AddressPhase | RamState::WaitLatency => {
self.tri_state_data_bus();
}
}
}
/// Transition to address phase state
fn start_ram_address_phase(&mut self) {
self.ram_state = RamState::AddressPhase;
// Clear address state for new operation
self.address_high_nibble = None;
self.address_low_nibble = None;
self.full_address_ready = false;
self.last_address = 0;
}
/// Handle address nibble latching during address phase
fn handle_address_latching(&mut self) {
let nibble = self.read_data_bus();
if self.address_high_nibble.is_none() {
self.address_high_nibble = Some(nibble);
println!("DEBUG: [{}] Addr Latch High | Nibble: 0x{:X} | State: {:?} | Cycles: {}",
self.base.name(), nibble, self.ram_state, self.get_cycle_count());
} else if self.address_low_nibble.is_none() {
self.address_low_nibble = Some(nibble);
println!("DEBUG: [{}] Addr Latch Low | High: 0x{:X} | Low: 0x{:X} | State: {:?} | Cycles: {}",
self.base.name(), self.address_high_nibble.unwrap(), nibble, self.ram_state, self.get_cycle_count());
self.assemble_full_address();
self.start_latency_wait();
}
}
/// Transition to latency wait state
fn start_latency_wait(&mut self) {
self.ram_state = RamState::WaitLatency;
self.address_latch_time = Some(Instant::now());
}
/// Handle latency timing during wait state
fn handle_latency_wait(&mut self) {
if let Some(latch_time) = self.address_latch_time {
if latch_time.elapsed() >= self.access_time {
self.start_data_operation();
}
}
}
/// Transition to data operation state
fn start_data_operation(&mut self) {
// Determine operation type based on control signals and address
let (sync, _cm, p0, _) = self.read_control_pins();
if sync && p0 && self.full_address_ready {
let address = self.last_address;
if address >= 0x14 && address <= 0x17 {
// Output port operation
self.ram_state = RamState::OutputPort;
} else {
// RAM read/write operation
// For now, assume read - write detection happens in data phase
self.ram_state = RamState::ReadData;
}
}
}
/// Handle data operations during ReadData or WriteData state
fn handle_data_operations(&mut self) {
let (sync, cm, p0, _) = self.read_control_pins();
if sync && p0 && self.full_address_ready {
let address = self.last_address;
if address == 0xFF {
// Status character operation
let data = self.read_data_bus();
if !cm {
// First cycle - read from status character
// For now, return status character 0 - this should be determined by instruction
self.write_data_bus(self.status_characters[0]);
println!("DEBUG: [{}] Status Char Read | Index: 0 | Data: 0x{:X} | State: {:?} | Cycles: {}",
self.base.name(), self.status_characters[0], self.ram_state, self.get_cycle_count());
} else {
// Second cycle - write to status character
// For now, write to status character 0 - this should be determined by instruction
let old_status = self.status_characters[0];
self.status_characters[0] = data & 0x0F;
self.ram_state = RamState::WriteData;
println!("DEBUG: [{}] Status Char Write | Index: 0 | Old: 0x{:X} | New: 0x{:X} | State: {:?} | Cycles: {}",
self.base.name(), old_status, data & 0x0F, self.ram_state, self.get_cycle_count());
}
} else if address < 80 {
// RAM operation
let data = self.read_data_bus();
if !cm {
// First cycle - read from RAM
let ram_data = self.memory[address as usize];
self.write_data_bus(ram_data);
println!("DEBUG: [{}] RAM Read | Addr: 0x{:02X} | Data: 0x{:X} | Bank: {} | State: {:?} | Cycles: {}",
self.base.name(), address, ram_data, self.bank_select, self.ram_state, self.get_cycle_count());
} else {
// Second cycle - write to RAM
let old_data = self.memory[address as usize];
self.memory[address as usize] = data & 0x0F;
self.ram_state = RamState::WriteData;
println!("DEBUG: [{}] RAM Write | Addr: 0x{:02X} | Old: 0x{:X} | New: 0x{:X} | Bank: {} | State: {:?} | Cycles: {}",
self.base.name(), address, old_data, data & 0x0F, self.bank_select, self.ram_state, self.get_cycle_count());
}
}
} else {
self.tri_state_data_bus();
}
}
/// Handle output port state operations
fn handle_output_port_state(&mut self) {
let (sync, cm, p0, _) = self.read_control_pins();
if sync && p0 && self.full_address_ready {
let address = self.last_address;
if address >= 0x14 && address <= 0x17 {
let port = (address - 0x14) as usize;
if !cm {
// First cycle - read from output port (not typical for 4002)
let port_data = self.output_ports[port];
self.write_data_bus(port_data);
println!("DEBUG: [{}] Output Port Read | Port: {} | Data: 0x{:X} | State: {:?} | Cycles: {}",
self.base.name(), port, port_data, self.ram_state, self.get_cycle_count());
} else {
// Second cycle - write to output port
let data = self.read_data_bus();
let old_port_data = self.output_ports[port];
self.handle_output_port_operation(port, data);
println!("DEBUG: [{}] Output Port Write | Port: {} | Old: 0x{:X} | New: 0x{:X} | State: {:?} | Cycles: {}",
self.base.name(), port, old_port_data, data & 0x0F, self.ram_state, self.get_cycle_count());
}
}
} else {
self.tri_state_data_bus();
}
}
/// Reset RAM state machine to idle
fn return_to_idle(&mut self) {
self.ram_state = RamState::Idle;
self.address_latch_time = None;
// Don't reset address nibbles and ready flag immediately - keep them for potential reuse
// They will be cleared when a new address operation starts
self.data_latch = None;
self.instruction_phase = false;
self.current_instruction = 0;
}
/// Handle special RAM instructions (bank select, status characters, etc.)
/// Hardware: These are handled during instruction cycles, not memory cycles
fn handle_special_instructions(&mut self, instruction: u8) {
match instruction {
// DCL (Designate Command Line) - selects RAM bank
0xF0..=0xF3 => {
self.bank_select = instruction & 0x03;
}
// SRC (Send Register Control) - sets output port address
0x20..=0x2F => {
// SRC instruction sets up output port addressing
// For now, this is handled by the output port operation logic
}
// Input latch operations (RDM - Read Data Memory)
0x60..=0x63 => {
self.handle_input_latch_operation();
}
_ => {
// Other instructions may be handled elsewhere
}
}
}
}
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;
}
// Debug: Show RAM state periodically to monitor for changes
static mut CYCLE_COUNTER: u64 = 0;
unsafe {
CYCLE_COUNTER += 1;
if CYCLE_COUNTER % 5000 == 0 { // Less frequent than CPU to avoid spam
println!("DEBUG: [{}] RAM State | Bank: {} | Status: {:?} | Ready: {} | LastAddr: 0x{:02X} | Cycles: {} | HighNib: {:?} | LowNib: {:?}",
self.base.name(), self.bank_select, self.ram_state, self.full_address_ready, self.last_address, self.get_cycle_count(), self.address_high_nibble, self.address_low_nibble);
}
}
// Handle both rising and falling edges for proper two-phase operation
let (phi1, phi2) = self.read_clock_pins();
let phi1_rising = phi1 == PinValue::High && self.prev_phi1 == PinValue::Low;
let phi1_falling = phi1 == PinValue::Low && self.prev_phi1 == PinValue::High;
let phi2_rising = phi2 == PinValue::High && self.prev_phi2 == PinValue::Low;
let phi2_falling = phi2 == PinValue::Low && self.prev_phi2 == PinValue::High;
// Update clock states for next edge detection
self.prev_phi1 = phi1;
self.prev_phi2 = phi2;
if phi1_rising {
// Φ1 Rising Edge: Address phase (CPU drives bus) - handle address latching
self.handle_phi1_rising();
}
if phi1_falling {
// Φ1 Falling Edge: End of address phase
self.handle_phi1_falling();
}
if phi2_rising {
// Φ2 Rising Edge: Data phase (RAM drives bus if ready) - handle data operations
self.handle_phi2_rising();
}
if phi2_falling {
// Φ2 Falling Edge: End of data phase - tri-state bus and return to idle
self.handle_phi2_falling();
}
// Handle special instructions when not in memory operation
let (sync, cm, p0, _) = self.read_control_pins();
if sync && !p0 && !cm {
// Instruction phase - handle special RAM instructions
let instruction = self.read_data_bus();
self.handle_special_instructions(instruction);
}
// Increment cycle count for debug output
self.cycle_count += 1;
}
/// 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_output_ports();
// Reset RAM operation state
self.address_latch_time = None;
}
fn is_running(&self) -> bool {
self.base.is_running()
}
}
impl RunnableComponent for Intel4002 {}
// Intel 4002 specific methods
impl Intel4002 {
/// Get the RAM size in nibbles
/// Returns: Total number of nibbles in RAM (80 for 4002)
pub fn get_ram_size(&self) -> usize {
self.memory.len()
}
/// Read a nibble from RAM at specified address
/// Parameters: address - RAM address (0-79)
/// 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 nibble to RAM at specified address
/// Parameters: address - RAM address (0-79), data - 4-bit data 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 & 0x0F;
Ok(())
} else {
Err("Address out of range (0-79)".to_string())
}
}
/// Get the current output port value
/// Parameters: port - Port number (0-3)
/// Returns: Some(data) if port valid, None if invalid
pub fn get_output_port(&self, port: usize) -> Option<u8> {
if port < 4 {
Some(self.output_ports[port])
} else {
None
}
}
/// Set an output port value
/// Parameters: port - Port number (0-3), data - 4-bit data to set
/// Returns: Ok(()) on success, Err(String) on failure
pub fn set_output_port(&mut self, port: usize, data: u8) -> Result<(), String> {
if port < 4 {
self.output_ports[port] = data & 0x0F;
self.update_output_ports();
Ok(())
} else {
Err("Port number out of range (0-3)".to_string())
}
}
/// Set the input latch value
/// Parameters: data - 4-bit input data
pub fn set_input_latch(&mut self, data: u8) {
self.input_latch = data & 0x0F;
}
/// Get the current input latch value
/// Returns: 4-bit input latch value
pub fn get_input_latch(&self) -> u8 {
self.input_latch
}
/// Get the current status character value
/// Parameters: index - Status character index (0-3)
/// Returns: 4-bit status character value
pub fn get_status_character(&self, index: usize) -> Option<u8> {
if index < 4 {
Some(self.status_characters[index])
} else {
None
}
}
/// Get all status characters
/// Returns: Array of 4 status character values
pub fn get_all_status_characters(&self) -> [u8; 4] {
self.status_characters
}
/// Get the current bank select value
/// Returns: 2-bit bank select value (0-3)
pub fn get_bank_select(&self) -> u8 {
self.bank_select
}
/// Get the RAM variant
/// Returns: The RAM variant (Type1 or Type2)
pub fn get_variant(&self) -> RamVariant {
self.variant
}
/// Get the current cycle count for the RAM component
/// Returns: Current cycle count since component start
pub fn get_cycle_count(&self) -> u64 {
self.cycle_count
}
/// Clear all RAM to zero
pub fn clear_ram(&mut self) {
self.memory = [0u8; 80]; // Clear 80 nibbles
}
/// Get all RAM data for a specific bank
/// Parameters: bank - Bank number (0-3)
/// Returns: Vector of 20 nibbles for the bank (4 banks × 20 nibbles = 80 total)
pub fn get_ram_bank(&self, bank: u8) -> Vec<u8> {
let bank = (bank & 0x03) as usize;
let start = bank * 20; // 20 nibbles per bank
let end = start + 20;
if end <= self.memory.len() {
self.memory[start..end].to_vec()
} else if start >= self.memory.len() {
Vec::new()
} else {
self.memory[start..].to_vec()
}
}
/// 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: {:?}, Bank: {}, High: {:?}, Low: {:?}, Address: 0x{:x}, Ready: {}",
test_name,
self.ram_state,
self.bank_select,
self.address_high_nibble,
self.address_low_nibble,
self.last_address,
self.full_address_ready
);
}
/// Setup helper for instruction testing
pub fn setup_instruction_test(&mut self, instruction: u8) {
self.current_instruction = instruction;
self.instruction_phase = true;
}
}
// Custom formatter for debugging
impl std::fmt::Display for RamState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RamState::Idle => write!(f, "Idle"),
RamState::AddressPhase => write!(f, "AddressPhase"),
RamState::WaitLatency => write!(f, "WaitLatency"),
RamState::ReadData => write!(f, "ReadData"),
RamState::WriteData => write!(f, "WriteData"),
RamState::OutputPort => write!(f, "OutputPort"),
}
}
}
#[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(), 80);
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 write/read
assert!(ram.write_ram(0, 0x0A).is_ok());
assert_eq!(ram.read_ram(0).unwrap(), 0x0A);
// Test out of bounds
assert!(ram.write_ram(80, 0x0F).is_err());
assert!(ram.read_ram(80).is_none());
}
#[test]
fn test_intel4002_output_ports() {
let mut ram = Intel4002::new("RAM_4002".to_string());
// Test output port operations
assert!(ram.set_output_port(0, 0x05).is_ok());
assert_eq!(ram.get_output_port(0).unwrap(), 0x05);
// Test invalid port
assert!(ram.set_output_port(4, 0x0F).is_err());
assert!(ram.get_output_port(4).is_none());
}
#[test]
fn test_intel4002_input_latch() {
let mut ram = Intel4002::new("RAM_4002".to_string());
ram.set_input_latch(0x07);
assert_eq!(ram.get_input_latch(), 0x07);
// Test that only lower 4 bits are stored
ram.set_input_latch(0x1F);
assert_eq!(ram.get_input_latch(), 0x0F);
}
#[test]
fn test_intel4002_ram_banks() {
let mut ram = Intel4002::new("RAM_4002".to_string());
// Write test data to different banks
ram.write_ram(0, 0x01).unwrap(); // Bank 0, address 0
ram.write_ram(20, 0x02).unwrap(); // Bank 1, address 0
ram.write_ram(40, 0x03).unwrap(); // Bank 2, address 0
let bank0 = ram.get_ram_bank(0);
assert_eq!(bank0.len(), 20); // 20 nibbles per bank
assert_eq!(bank0[0], 0x01);
let bank1 = ram.get_ram_bank(1);
assert_eq!(bank1.len(), 20); // 20 nibbles per bank
assert_eq!(bank1[0], 0x02);
let bank2 = ram.get_ram_bank(2);
assert_eq!(bank2.len(), 20); // 20 nibbles per bank
assert_eq!(bank2[0], 0x03);
}
#[test]
fn test_intel4002_clear_ram() {
let mut ram = Intel4002::new("RAM_4002".to_string());
ram.write_ram(0, 0x0A).unwrap();
ram.write_ram(10, 0x0B).unwrap();
assert_eq!(ram.read_ram(0).unwrap(), 0x0A);
assert_eq!(ram.read_ram(10).unwrap(), 0x0B);
ram.clear_ram();
assert_eq!(ram.read_ram(0).unwrap(), 0);
assert_eq!(ram.read_ram(10).unwrap(), 0);
}
#[test]
fn test_configurable_access_time() {
let mut ram = Intel4002::new("RAM_4002".to_string());
// Test default access time
assert_eq!(ram.get_access_time(), 500);
// Test setting custom access time
ram.set_access_time(100);
assert_eq!(ram.get_access_time(), 100);
// Test constructor with custom access time
let fast_ram = Intel4002::new_with_access_time("FAST_RAM".to_string(), 1);
assert_eq!(fast_ram.get_access_time(), 1);
assert_eq!(fast_ram.name(), "FAST_RAM");
}
#[test]
fn test_address_latching() {
let mut ram = Intel4002::new_with_access_time("RAM_4002".to_string(), 1);
// Start the component so it can process updates
ram.base.set_running(true);
assert!(ram.is_running());
// Get pin references
let sync_pin = ram.get_pin("SYNC").unwrap();
let p0_pin = ram.get_pin("P0").unwrap();
let phi1_pin = ram.get_pin("PHI1").unwrap();
let d0_pin = ram.get_pin("D0").unwrap();
let d1_pin = ram.get_pin("D1").unwrap();
let d2_pin = ram.get_pin("D2").unwrap();
let d3_pin = ram.get_pin("D3").unwrap();
// Initialize clock pins to Low
{
let mut phi1_guard = phi1_pin.lock().unwrap();
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
}
// Set up RAM operation: SYNC=1, CM-RAM=1
{
let mut sync_guard = sync_pin.lock().unwrap();
let mut p0_guard = p0_pin.lock().unwrap();
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
p0_guard.set_driver(Some("TEST".to_string()), PinValue::High);
}
// Set address high nibble (0x0) on data bus
{
let mut d0_guard = d0_pin.lock().unwrap();
let mut d1_guard = d1_pin.lock().unwrap();
let mut d2_guard = d2_pin.lock().unwrap();
let mut d3_guard = d3_pin.lock().unwrap();
d0_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
d1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
d2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
d3_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
}
// Debug: Check pin states before first update
println!("DEBUG: Before first update - SYNC: {:?}, P0: {:?}, PHI1: {:?}",
sync_pin.lock().unwrap().read(),
p0_pin.lock().unwrap().read(),
phi1_pin.lock().unwrap().read());
// Debug: Check conditions that would trigger RAM operation
let (sync, _cm, p0, _) = ram.read_control_pins();
println!("DEBUG: Control pins - sync: {}, p0: {}, combined: {}", sync, p0, sync && p0);
// Debug: Check initial clock state
println!("DEBUG: Before first update - prev_phi1: {:?}", ram.prev_phi1);
// High nibble
phi1_pin
.lock()
.unwrap()
.set_driver(Some("TEST".into()), PinValue::High);
ram.update(); // rising edge -> latch high nibble
// Debug: Check clock state after first update
println!("DEBUG: After first update - prev_phi1: {:?}", ram.prev_phi1);
phi1_pin
.lock()
.unwrap()
.set_driver(Some("TEST".into()), PinValue::Low);
ram.update(); // falling edge
// Should have transitioned to AddressPhase and latched high nibble
assert_eq!(ram.ram_state, RamState::AddressPhase);
assert_eq!(ram.address_high_nibble, Some(0x0));
// Note: full_address_ready should be false until both nibbles are assembled
// Set address low nibble (0x1) on data bus - different from high nibble
{
let mut d0_guard = d0_pin.lock().unwrap();
let mut d1_guard = d1_pin.lock().unwrap();
let mut d2_guard = d2_pin.lock().unwrap();
let mut d3_guard = d3_pin.lock().unwrap();
d0_guard.set_driver(Some("TEST".into()), PinValue::High); // Bit 0 = 1
d1_guard.set_driver(Some("TEST".into()), PinValue::Low);
d2_guard.set_driver(Some("TEST".into()), PinValue::Low);
d3_guard.set_driver(Some("TEST".into()), PinValue::Low);
}
// Debug: Check state before low nibble
println!("DEBUG: Before low nibble - High: {:?}, Low: {:?}, Ready: {}",
ram.address_high_nibble, ram.address_low_nibble, ram.full_address_ready);
// Low nibble
phi1_pin
.lock()
.unwrap()
.set_driver(Some("TEST".into()), PinValue::High);
ram.update(); // rising edge -> latch low nibble
// Debug: Check state after low nibble
println!("DEBUG: After low nibble - High: {:?}, Low: {:?}, Ready: {}",
ram.address_high_nibble, ram.address_low_nibble, ram.full_address_ready);
phi1_pin
.lock()
.unwrap()
.set_driver(Some("TEST".into()), PinValue::Low);
ram.update(); // falling edge
// Should have assembled full address and transitioned to WaitLatency
assert_eq!(ram.last_address, 0x01); // High nibble 0x0, low nibble 0x1 = 0x01
assert_eq!(ram.full_address_ready, true); // Now both nibbles are assembled
assert_eq!(ram.ram_state, RamState::WaitLatency);
}
#[test]
fn test_reset_behavior() {
let mut ram = Intel4002::new_with_access_time("RAM_4002".to_string(), 1);
// Set up some state first
let sync_pin = ram.get_pin("SYNC").unwrap();
let p0_pin = ram.get_pin("P0").unwrap();
let reset_pin = ram.get_pin("RESET").unwrap();
// Set RAM operation active
{
let mut sync_guard = sync_pin.lock().unwrap();
let mut p0_guard = p0_pin.lock().unwrap();
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
p0_guard.set_driver(Some("TEST".to_string()), PinValue::High);
}
// Trigger some state changes
ram.update();
// Now assert RESET
{
let mut reset_guard = reset_pin.lock().unwrap();
reset_guard.set_driver(Some("TEST".to_string()), PinValue::High);
}
// Update with RESET high - should clear all state
ram.update();
// Verify all state is cleared
assert_eq!(ram.get_output_port(0).unwrap(), 0);
assert_eq!(ram.get_input_latch(), 0);
assert_eq!(ram.get_bank_select(), 0);
// Verify data bus is tri-stated
let d0_pin = ram.get_pin("D0").unwrap();
assert_eq!(d0_pin.lock().unwrap().read(), PinValue::HighZ);
}
#[test]
fn test_output_port_operations() {
let mut ram = Intel4002::new("RAM_4002".to_string());
// Test output port operations
ram.handle_output_port_operation(0, 0x05);
assert_eq!(ram.get_output_port(0).unwrap(), 0x05);
ram.handle_output_port_operation(1, 0x0A);
assert_eq!(ram.get_output_port(1).unwrap(), 0x0A);
// Test invalid port (should not crash)
ram.handle_output_port_operation(4, 0x0F);
}
#[test]
fn test_input_latch_operations() {
let mut ram = Intel4002::new("RAM_4002".to_string());
// Set input latch
ram.set_input_latch(0x07);
// Mock data bus read to return input latch value
// In real test, this would involve setting up pin states
// For now, just verify the latch is set correctly
assert_eq!(ram.get_input_latch(), 0x07);
}
#[test]
fn test_ram_initialization() {
let mut ram = Intel4002::new("RAM_4002".to_string());
let test_data = vec![0x01, 0x02, 0x03, 0x04, 0x05];
assert!(ram.initialize_ram(&test_data).is_ok());
assert_eq!(ram.read_ram(0).unwrap(), 0x01);
assert_eq!(ram.read_ram(1).unwrap(), 0x02);
assert_eq!(ram.read_ram(2).unwrap(), 0x03);
assert_eq!(ram.read_ram(3).unwrap(), 0x04);
assert_eq!(ram.read_ram(4).unwrap(), 0x05);
// Test overflow
let large_data = vec![0u8; 81]; // More than 80 nibbles
assert!(ram.initialize_ram(&large_data).is_err());
}
#[test]
fn test_ram_state_transitions() {
let ram = Intel4002::new_with_access_time("RAM_4002".to_string(), 1);
// Initially should be in idle state
assert_eq!(ram.ram_state, RamState::Idle);
// Test state transitions through debug function
ram.debug_state_transitions("INITIAL");
assert_eq!(ram.ram_state, RamState::Idle);
}
#[test]
fn test_pin_configuration() {
let ram = Intel4002::new("RAM_4002".to_string());
// Test that all expected pins are present
assert!(ram.get_pin("D0").is_ok());
assert!(ram.get_pin("D1").is_ok());
assert!(ram.get_pin("D2").is_ok());
assert!(ram.get_pin("D3").is_ok());
assert!(ram.get_pin("O0").is_ok());
assert!(ram.get_pin("O1").is_ok());
assert!(ram.get_pin("O2").is_ok());
assert!(ram.get_pin("O3").is_ok());
assert!(ram.get_pin("SYNC").is_ok());
assert!(ram.get_pin("CM").is_ok());
assert!(ram.get_pin("P0").is_ok());
assert!(ram.get_pin("RESET").is_ok());
assert!(ram.get_pin("PHI1").is_ok());
assert!(ram.get_pin("PHI2").is_ok());
}
#[test]
fn test_dcl_instruction() {
let mut ram = Intel4002::new("TEST".to_string());
// Simulate DCL instruction on data bus during instruction phase
ram.setup_instruction_test(0xF1); // DCL to select bank 1
ram.handle_special_instructions(0xF1);
assert_eq!(ram.get_bank_select(), 1);
}
#[test]
fn test_src_instruction() {
let mut ram = Intel4002::new("TEST".to_string());
// SRC instruction sets up output port addressing
ram.setup_instruction_test(0x25); // SRC with specific address
// Verify output port addressing is set up correctly
// This would require more complex setup in a real implementation
}
}