1375 lines
49 KiB
Rust
1375 lines
49 KiB
Rust
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
|
||
}
|
||
}
|