Completed instruction set of 4004 Improved accuracy of 4002 and 4004 implementations
1840 lines
74 KiB
Rust
1840 lines
74 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::pin::{Pin, PinValue};
|
|
|
|
/// Intel 4001 - 256-byte ROM with integrated I/O
|
|
/// Part of the MCS-4 family, designed to work with Intel 4004 CPU
|
|
/// Features 256 bytes of mask-programmable ROM and 4 I/O pins
|
|
///
|
|
/// Hardware Deviations:
|
|
/// - I/O read results are written to D0-D3 for test convenience
|
|
/// (real hardware reads directly from I/O pins)
|
|
/// - SYNC logic simplified for unit testing (may need refinement for CPU integration)
|
|
pub struct Intel4001 {
|
|
base: BaseComponent,
|
|
memory: Vec<u8>, // 256-byte ROM storage
|
|
last_address: u16, // Last accessed memory address
|
|
access_time: Duration, // ROM access latency (500ns)
|
|
output_latch: u8, // 4-bit output latch for I/O operations
|
|
input_latch: u8, // 4-bit input latch for I/O operations
|
|
io_mode: IoMode, // Current I/O mode configuration
|
|
// Clock edge detection
|
|
prev_phi1: PinValue, // Previous Φ1 clock state for edge detection
|
|
prev_phi2: PinValue, // Previous Φ2 clock state for edge detection
|
|
// Access latency modeling
|
|
address_latch_time: Option<Instant>, // Timestamp when address was latched
|
|
// Two-phase addressing for 8-bit address
|
|
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
|
|
// Memory operation state machine
|
|
memory_state: MemoryState, // Current state of memory operation
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
/// I/O mode configuration for the 4001 ROM
|
|
/// Determines how the I/O pins are configured during read/write operations
|
|
enum IoMode {
|
|
Input, // I/O pins configured as inputs (RDM instruction)
|
|
Output, // I/O pins configured as outputs (WRM instruction)
|
|
Bidirectional, // I/O pins bidirectional (not used in 4001)
|
|
}
|
|
|
|
/// Memory operation state machine states
|
|
/// Tracks the current phase of memory access operations
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
enum MemoryState {
|
|
Idle, // No memory operation in progress
|
|
AddressPhase, // Currently latching address nibbles
|
|
WaitLatency, // Address latched, waiting for access time
|
|
DriveData, // Latency elapsed, driving data on bus
|
|
}
|
|
|
|
impl Intel4001 {
|
|
/// Create a new Intel 4001 ROM with specified access time
|
|
/// Parameters: name - Component identifier, access_time_ns - Memory access time in nanoseconds
|
|
/// Returns: New Intel4001 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 4001 ROM with custom access time (for testing)
|
|
/// Parameters: name - Component identifier, access_time_ns - Memory access time in nanoseconds
|
|
/// Returns: New Intel4001 instance with configurable access timing
|
|
pub fn new_with_access_time(name: String, access_time_ns: u64) -> Self {
|
|
// Intel 4001 has 256 bytes of ROM
|
|
let rom_size = 256;
|
|
|
|
// Intel 4001 pinout (per datasheet):
|
|
// - 4 data pins (D0-D3) for multiplexed address/data
|
|
// - 4 I/O pins (IO0-IO3)
|
|
// - 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 ROM 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 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);
|
|
let memory = vec![0u8; rom_size];
|
|
|
|
Intel4001 {
|
|
base: BaseComponent::new(name, pins),
|
|
memory,
|
|
last_address: 0,
|
|
access_time: Duration::from_nanos(access_time_ns),
|
|
output_latch: 0,
|
|
input_latch: 0,
|
|
io_mode: IoMode::Input,
|
|
prev_phi1: PinValue::Low,
|
|
prev_phi2: PinValue::Low,
|
|
address_latch_time: None,
|
|
address_high_nibble: None,
|
|
address_low_nibble: None,
|
|
full_address_ready: false,
|
|
memory_state: MemoryState::Idle,
|
|
}
|
|
}
|
|
|
|
/// 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
|
|
}
|
|
|
|
/// Load binary data into ROM at specified offset
|
|
/// Parameters: data - Binary data to load, offset - Starting address
|
|
/// Returns: Ok(()) on success, Err(String) on failure
|
|
pub fn load_rom_data(&mut self, data: Vec<u8>, offset: usize) -> Result<(), String> {
|
|
if offset + data.len() > self.memory.len() {
|
|
return Err(format!(
|
|
"Data exceeds ROM capacity: offset {} + data length {} > ROM size {}",
|
|
offset,
|
|
data.len(),
|
|
self.memory.len()
|
|
));
|
|
}
|
|
|
|
self.memory[offset..offset + data.len()].copy_from_slice(&data);
|
|
Ok(())
|
|
}
|
|
|
|
/// Load hexadecimal data into ROM at specified offset
|
|
/// Parameters: hex_data - Space-separated hex bytes, offset - Starting address
|
|
/// Returns: Ok(()) on success, Err(String) on failure
|
|
pub fn load_from_hex(&mut self, hex_data: &str, offset: usize) -> Result<(), String> {
|
|
let bytes: Result<Vec<u8>, _> = hex_data
|
|
.split_whitespace()
|
|
.map(|s| u8::from_str_radix(s.trim(), 16))
|
|
.collect();
|
|
|
|
match bytes {
|
|
Ok(data) => self.load_rom_data(data, offset),
|
|
Err(e) => Err(format!("Invalid hex data: {}", e)),
|
|
}
|
|
}
|
|
|
|
/// 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
|
|
}
|
|
|
|
/// 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) {
|
|
println!("DEBUG: {} - write_data_bus called with data 0x{:x}", self.base.name(), data);
|
|
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 = (data >> i) & 1;
|
|
let pin_value = if bit_value == 1 {
|
|
PinValue::High
|
|
} else {
|
|
PinValue::Low
|
|
};
|
|
println!("DEBUG: {} - Setting D{} (bit {}) to {:?} (bit_value={})",
|
|
self.base.name(), i, i, pin_value, bit_value);
|
|
// Use a unique driver name to avoid conflicts with test drivers
|
|
pin_guard.set_driver(Some(format!("{}_DATA", self.base.name())), pin_value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn read_io_pins(&self) -> u8 {
|
|
let mut data = 0;
|
|
|
|
for i in 0..4 {
|
|
if let Ok(pin) = self.base.get_pin(&format!("IO{}", i)) {
|
|
if let Ok(pin_guard) = pin.lock() {
|
|
if pin_guard.read() == PinValue::High {
|
|
data |= 1 << i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
data
|
|
}
|
|
|
|
fn write_io_pins(&self, data: u8) {
|
|
for i in 0..4 {
|
|
if let Ok(pin) = self.base.get_pin(&format!("IO{}", i)) {
|
|
if let Ok(mut pin_guard) = pin.lock() {
|
|
let bit_value = (data >> i) & 1;
|
|
let pin_value = if bit_value == 1 {
|
|
PinValue::High
|
|
} else {
|
|
PinValue::Low
|
|
};
|
|
pin_guard.set_driver(Some(self.base.name()), pin_value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Set data bus to high-impedance state to avoid bus contention
|
|
/// CRITICAL: Must be called whenever ROM 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() {
|
|
// Use the same unique driver name format for consistency
|
|
pin_guard.set_driver(Some(format!("{}_DATA", self.base.name())), PinValue::HighZ);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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 as u16) << 4) | (low as u16);
|
|
self.full_address_ready = true;
|
|
self.address_latch_time = Some(Instant::now());
|
|
|
|
// Clear nibble storage for next address
|
|
self.address_high_nibble = None;
|
|
self.address_low_nibble = None;
|
|
}
|
|
}
|
|
|
|
/// 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();
|
|
|
|
// 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.
|
|
// ROM access: CM=1 (chip_select), CI=0 (!io_select)
|
|
// I/O access: CM=1 (chip_select), CI=1 (io_select)
|
|
let (sync, chip_select, io_select, _) = self.read_control_pins();
|
|
if sync && chip_select && !io_select {
|
|
// Start memory address phase on Φ1 rising edge
|
|
self.start_memory_address_phase();
|
|
}
|
|
|
|
// Handle I/O operations (higher priority than memory operations)
|
|
// I/O operations: CM=1 (chip_select), CI=1 (io_select)
|
|
// Note: SYNC is NOT required for I/O - only marks instruction fetch (ROM access)
|
|
self.handle_io_operation();
|
|
|
|
// Handle memory address phase operations during Φ1
|
|
self.handle_memory_address_operations();
|
|
}
|
|
|
|
/// Handle Φ1 falling edge - End of address phase
|
|
/// Hardware: Φ1 low = End of CPU address driving phase
|
|
fn handle_phi1_falling(&mut self) {
|
|
// Currently no specific operations needed on Φ1 falling
|
|
// Address latching happens on Φ1 rising, data driving on Φ2 rising
|
|
}
|
|
|
|
/// Handle Φ2 rising edge - Data phase
|
|
/// Hardware: Φ2 high = Peripherals drive bus with data
|
|
/// Focus: Data driving operations
|
|
fn handle_phi2_rising(&mut self) {
|
|
// Handle memory data phase operations during Φ2
|
|
self.handle_memory_data_operations();
|
|
}
|
|
|
|
/// 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) {
|
|
// Handle memory cleanup operations when Φ2 falls
|
|
self.handle_memory_cleanup_operations();
|
|
}
|
|
|
|
fn tri_state_io_pins(&self) {
|
|
for i in 0..4 {
|
|
if let Ok(pin) = self.base.get_pin(&format!("IO{}", i)) {
|
|
if let Ok(mut pin_guard) = pin.lock() {
|
|
pin_guard.set_driver(Some(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)
|
|
}
|
|
|
|
fn is_phi1_rising_edge(&self) -> bool {
|
|
let (phi1, _) = self.read_clock_pins();
|
|
phi1 == PinValue::High && self.prev_phi1 == PinValue::Low
|
|
}
|
|
|
|
fn is_phi1_falling_edge(&self) -> bool {
|
|
let (phi1, _) = self.read_clock_pins();
|
|
phi1 == PinValue::Low && self.prev_phi1 == PinValue::High
|
|
}
|
|
|
|
fn is_phi2_rising_edge(&self) -> bool {
|
|
let (_, phi2) = self.read_clock_pins();
|
|
phi2 == PinValue::High && self.prev_phi2 == PinValue::Low
|
|
}
|
|
|
|
fn is_phi2_falling_edge(&self) -> bool {
|
|
let (_, phi2) = self.read_clock_pins();
|
|
phi2 == PinValue::Low && self.prev_phi2 == PinValue::High
|
|
}
|
|
|
|
fn update_clock_states(&mut self) {
|
|
let (phi1, phi2) = self.read_clock_pins();
|
|
self.prev_phi1 = phi1;
|
|
self.prev_phi2 = phi2;
|
|
}
|
|
|
|
/// Read all control pins from CPU
|
|
/// Returns: (sync, chip_select, io_select, reset)
|
|
/// Hardware: Control pins determine operation type and chip state
|
|
fn read_control_pins(&self) -> (bool, bool, bool, bool) {
|
|
// SYNC: Marks the start of an instruction cycle
|
|
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
|
|
};
|
|
|
|
// CM: Chip select (must be HIGH for ROM access)
|
|
let chip_select = 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
|
|
};
|
|
|
|
// CI: I/O select (distinguishes I/O vs memory when chip selected)
|
|
let io_select = if let Ok(pin) = self.base.get_pin("CI") {
|
|
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, chip_select, io_select, reset)
|
|
}
|
|
|
|
/// Handle system reset signal
|
|
/// Hardware: RESET pin clears all internal state and tri-states outputs
|
|
fn handle_reset(&mut self) {
|
|
let (_, _, _, reset) = self.read_control_pins();
|
|
if reset {
|
|
// RESET is high - clear all internal state
|
|
self.output_latch = 0;
|
|
self.input_latch = 0;
|
|
self.io_mode = IoMode::Input;
|
|
self.tri_state_data_bus();
|
|
self.tri_state_io_pins();
|
|
|
|
// Reset memory operation state
|
|
self.address_latch_time = None;
|
|
self.memory_state = MemoryState::Idle;
|
|
|
|
// Reset two-phase addressing state
|
|
self.address_high_nibble = None;
|
|
self.address_low_nibble = None;
|
|
self.full_address_ready = false;
|
|
}
|
|
}
|
|
|
|
/// Handle I/O port operations
|
|
/// Hardware: I/O operations occur during I/O instructions (WRM/RDM)
|
|
/// Direction determined by CPU instruction, not manual configuration
|
|
/// Note: I/O pins remain driven after WRM until another WRM or reset.
|
|
/// This matches real 4001 hardware where I/O ports are latched.
|
|
/// Hardware deviation: Model writes IO latch to D0-D3 for test convenience,
|
|
/// but real hardware reads I/O pins directly.
|
|
///
|
|
/// I/O Latch Persistence:
|
|
/// - Output latch persists after WRM until next WRM or reset
|
|
/// - Input latch overwrites every RDM (I/O input pins are live reads)
|
|
fn handle_io_operation(&mut self) {
|
|
let (_sync, chip_select, io_select, _) = self.read_control_pins();
|
|
|
|
// I/O operations occur when: CM=1 (chip_select), CI=1 (io_select)
|
|
// Note: SYNC is NOT required for I/O - only marks instruction fetch (ROM access)
|
|
if chip_select && io_select {
|
|
let _address_low = self.read_data_bus(); // I/O port address (lower 4 bits)
|
|
|
|
// Real 4001 I/O behavior based on CPU instructions:
|
|
// RDM instruction: Read from I/O port
|
|
self.input_latch = self.read_io_pins();
|
|
// Hardware deviation: Write to D0-D3 for test convenience
|
|
self.write_data_bus(self.input_latch);
|
|
self.io_mode = IoMode::Input;
|
|
}
|
|
}
|
|
|
|
/// Handle memory address-related operations during Φ1
|
|
/// Hardware-accurate: Φ1 is when CPU drives address, so we handle address latching
|
|
/// Memory operation start is checked in handle_phi1_rising() on SYNC high + Φ1 rising
|
|
/// Focus: Address latching and setup operations
|
|
fn handle_memory_address_operations(&mut self) {
|
|
match self.memory_state {
|
|
MemoryState::Idle => {
|
|
// In idle state, ensure bus is tri-stated
|
|
// Memory operation start is checked in handle_phi1_rising()
|
|
self.tri_state_data_bus();
|
|
}
|
|
|
|
MemoryState::AddressPhase => {
|
|
// Currently latching address nibbles during Φ1
|
|
self.handle_address_latching();
|
|
}
|
|
|
|
MemoryState::WaitLatency => {
|
|
// Address latched, waiting for access latency
|
|
// Check if latency has elapsed and we can transition to data phase
|
|
self.handle_latency_wait();
|
|
}
|
|
|
|
MemoryState::DriveData => {
|
|
// Data phase should be handled by Φ2, not Φ1
|
|
// Just ensure bus is tri-stated during wrong phase
|
|
self.tri_state_data_bus();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Handle memory data-related operations during Φ2
|
|
/// Hardware-accurate: Φ2 is when peripherals drive data, so we handle data driving
|
|
/// Focus: Data driving operations
|
|
fn handle_memory_data_operations(&mut self) {
|
|
println!("DEBUG: {} - handle_memory_data_operations: state={:?}, address_ready={}",
|
|
self.base.name(), self.memory_state, self.full_address_ready);
|
|
match self.memory_state {
|
|
MemoryState::Idle => {
|
|
// During data phase, idle state means tri-state the bus
|
|
println!("DEBUG: {} - In Idle state, tri-stating", self.base.name());
|
|
self.tri_state_data_bus();
|
|
}
|
|
|
|
MemoryState::AddressPhase => {
|
|
// Address phase should be handled by Φ1, not Φ2
|
|
// Tri-state bus during wrong phase
|
|
println!("DEBUG: {} - In AddressPhase during Φ2, tri-stating", self.base.name());
|
|
self.tri_state_data_bus();
|
|
}
|
|
|
|
MemoryState::WaitLatency => {
|
|
// Address latched, waiting for access latency
|
|
// Check if latency has elapsed and we can transition to data phase
|
|
println!("DEBUG: {} - In WaitLatency, checking latency", self.base.name());
|
|
self.handle_latency_wait();
|
|
// If we transitioned to DriveData, handle data driving
|
|
if self.memory_state == MemoryState::DriveData {
|
|
println!("DEBUG: {} - Transitioned to DriveData, calling handle_data_driving", self.base.name());
|
|
self.handle_data_driving();
|
|
} else {
|
|
println!("DEBUG: {} - Still in WaitLatency after handle_latency_wait", self.base.name());
|
|
}
|
|
}
|
|
|
|
MemoryState::DriveData => {
|
|
// Latency elapsed, drive data on bus during Φ2
|
|
// Data will remain on bus until Φ2 falling edge
|
|
println!("DEBUG: {} - In DriveData state, calling handle_data_driving", self.base.name());
|
|
self.handle_data_driving();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Handle memory cleanup operations on Φ2 falling edge
|
|
/// Hardware-accurate: Clean up when Φ2 falls (end of data phase)
|
|
/// Focus: Tri-state bus and return to idle
|
|
fn handle_memory_cleanup_operations(&mut self) {
|
|
match self.memory_state {
|
|
MemoryState::DriveData => {
|
|
// End of data phase - tri-state bus and return to idle
|
|
self.tri_state_data_bus();
|
|
self.return_to_idle();
|
|
}
|
|
|
|
MemoryState::Idle | MemoryState::AddressPhase | MemoryState::WaitLatency => {
|
|
// For other states, just ensure bus is tri-stated
|
|
self.tri_state_data_bus();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Transition to address phase state
|
|
/// Hardware: Start of memory read cycle, CPU begins providing address
|
|
fn start_memory_address_phase(&mut self) {
|
|
self.memory_state = MemoryState::AddressPhase;
|
|
// Don't clear nibbles here - they should only be cleared after successful assembly
|
|
// or when explicitly resetting the state machine
|
|
self.full_address_ready = false;
|
|
}
|
|
|
|
/// Handle address nibble latching during address phase
|
|
/// Hardware: CPU provides 8-bit address as two 4-bit nibbles
|
|
fn handle_address_latching(&mut self) {
|
|
let nibble = self.read_data_bus();
|
|
|
|
if self.address_high_nibble.is_none() {
|
|
// First cycle: latch high nibble (bits 7-4)
|
|
self.address_high_nibble = Some(nibble);
|
|
} else if self.address_low_nibble.is_none() {
|
|
// Second cycle: latch low nibble (bits 3-0) and transition to latency wait
|
|
self.address_low_nibble = Some(nibble);
|
|
self.assemble_full_address();
|
|
self.start_latency_wait();
|
|
}
|
|
// Note: If both nibbles are the same value (like 0x0), the logic still works correctly
|
|
// because we check for None rather than comparing values
|
|
}
|
|
|
|
/// Transition to latency wait state
|
|
/// Hardware: Address captured, start 500ns access time before data available
|
|
fn start_latency_wait(&mut self) {
|
|
self.memory_state = MemoryState::WaitLatency;
|
|
self.address_latch_time = Some(Instant::now());
|
|
}
|
|
|
|
/// Handle latency timing during wait state
|
|
/// Hardware: ROM needs 500ns to access data after address is latched
|
|
fn handle_latency_wait(&mut self) {
|
|
if let Some(latch_time) = self.address_latch_time {
|
|
let elapsed = latch_time.elapsed();
|
|
println!("DEBUG: {} - handle_latency_wait: elapsed={:?}, access_time={:?}, ready={}",
|
|
self.base.name(), elapsed, self.access_time, self.full_address_ready);
|
|
if elapsed >= self.access_time {
|
|
// Latency elapsed, transition to data driving
|
|
// Data will be driven on next Φ2 rising edge
|
|
println!("DEBUG: {} - Latency elapsed, transitioning to DriveData", self.base.name());
|
|
self.start_data_driving();
|
|
}
|
|
} else {
|
|
println!("DEBUG: {} - handle_latency_wait: no latch_time set", self.base.name());
|
|
}
|
|
}
|
|
|
|
/// Transition to data driving state
|
|
/// Hardware: Access latency complete, ready to drive data on next Φ2 cycle
|
|
fn start_data_driving(&mut self) {
|
|
self.memory_state = MemoryState::DriveData;
|
|
}
|
|
|
|
/// Handle data driving during DriveData state
|
|
/// Hardware: ROM drives data bus when all conditions are met
|
|
/// Data stays on bus during Φ2 high period, tri-stated on Φ2 falling edge
|
|
fn handle_data_driving(&mut self) {
|
|
let (sync, chip_select, io_select, _) = self.read_control_pins();
|
|
|
|
println!("DEBUG: {} - handle_data_driving: SYNC={}, CM={}, CI={}, Address_Ready={}",
|
|
self.base.name(), sync, chip_select, io_select, self.full_address_ready);
|
|
|
|
// Memory read: CM=1 (chip_select), CI=0 (!io_select), valid address
|
|
if sync && chip_select && !io_select && self.full_address_ready {
|
|
// All conditions met: drive data on bus
|
|
// Data will remain on bus until Φ2 falling edge
|
|
let address = self.last_address;
|
|
if (address as usize) < self.memory.len() {
|
|
let data = self.memory[address as usize];
|
|
println!("DEBUG: {} - All conditions met, driving data 0x{:x} to address 0x{:x}", self.base.name(), data, address);
|
|
self.write_data_bus(data);
|
|
// Note: Don't call return_to_idle() here - wait for Φ2 falling edge
|
|
} else {
|
|
// Invalid address, tri-state
|
|
println!("DEBUG: {} - Invalid address 0x{:x}, tri-stating", self.base.name(), address);
|
|
self.tri_state_data_bus();
|
|
}
|
|
} else {
|
|
// Bus contention guard: ROM should not drive when conditions not met
|
|
// In real hardware, this would cause a short if CPU is still driving
|
|
if self.full_address_ready {
|
|
println!("DEBUG: {} - Bus contention detected! ROM attempting to drive data bus when conditions not met (SYNC={}, CM={}, CI={}, Address_Ready={})",
|
|
self.base.name(), sync, chip_select, io_select, self.full_address_ready);
|
|
}
|
|
// Conditions not met, tri-state
|
|
println!("DEBUG: {} - Conditions not met, tri-stating (SYNC={}, CM={}, CI={}, Address_Ready={})",
|
|
self.base.name(), sync, chip_select, io_select, self.full_address_ready);
|
|
self.tri_state_data_bus();
|
|
}
|
|
}
|
|
|
|
/// Reset memory state machine to idle
|
|
/// Hardware: Called when memory operation completes or is interrupted
|
|
fn return_to_idle(&mut self) {
|
|
self.memory_state = MemoryState::Idle;
|
|
self.address_latch_time = None;
|
|
self.address_high_nibble = None;
|
|
self.address_low_nibble = None;
|
|
self.full_address_ready = false;
|
|
}
|
|
}
|
|
|
|
impl Component for Intel4001 {
|
|
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) {
|
|
// 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 (ROM drives bus if ready) - handle data driving
|
|
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();
|
|
}
|
|
}
|
|
|
|
/// 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_io_pins();
|
|
|
|
// Reset memory operation state
|
|
self.address_latch_time = None;
|
|
}
|
|
|
|
fn is_running(&self) -> bool {
|
|
self.base.is_running()
|
|
}
|
|
}
|
|
|
|
impl RunnableComponent for Intel4001 {}
|
|
|
|
// Intel 4001 specific methods
|
|
impl Intel4001 {
|
|
/// Get the ROM size in bytes
|
|
/// Returns: Total number of bytes in ROM (256 for 4001)
|
|
pub fn get_rom_size(&self) -> usize {
|
|
self.memory.len()
|
|
}
|
|
|
|
/// Read a byte from ROM at specified address
|
|
/// Parameters: address - 8-bit address (0-255)
|
|
/// Returns: Some(data) if address valid, None if out of bounds
|
|
pub fn read_rom(&self, address: u8) -> Option<u8> {
|
|
if (address as usize) < self.memory.len() {
|
|
Some(self.memory[address as usize])
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
/// Get the current output latch value
|
|
/// Returns: 4-bit value last written to I/O ports
|
|
pub fn get_output_latch(&self) -> u8 {
|
|
self.output_latch
|
|
}
|
|
|
|
/// Get the current input latch value
|
|
/// Returns: 4-bit value last read from I/O ports
|
|
pub fn get_input_latch(&self) -> u8 {
|
|
self.input_latch
|
|
}
|
|
|
|
/// 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.memory_state, self.address_high_nibble, self.address_low_nibble,
|
|
self.last_address, self.full_address_ready, self.address_latch_time);
|
|
}
|
|
}
|
|
|
|
// Custom formatter for debugging
|
|
impl std::fmt::Display for IoMode {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
IoMode::Input => write!(f, "Input"),
|
|
IoMode::Output => write!(f, "Output"),
|
|
IoMode::Bidirectional => write!(f, "Bidirectional"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_intel4001_creation() {
|
|
let rom = Intel4001::new("ROM_4001".to_string());
|
|
assert_eq!(rom.name(), "ROM_4001");
|
|
assert_eq!(rom.get_rom_size(), 256);
|
|
assert_eq!(rom.get_access_time(), 500); // Default 500ns
|
|
assert!(!rom.is_running());
|
|
}
|
|
|
|
#[test]
|
|
fn test_intel4001_rom_loading() {
|
|
let mut rom = Intel4001::new("ROM_4001".to_string());
|
|
|
|
let test_data = vec![0x12, 0x34, 0x56, 0x78];
|
|
assert!(rom.load_rom_data(test_data.clone(), 0).is_ok());
|
|
|
|
assert_eq!(rom.read_rom(0).unwrap(), 0x12);
|
|
assert_eq!(rom.read_rom(1).unwrap(), 0x34);
|
|
assert_eq!(rom.read_rom(2).unwrap(), 0x56);
|
|
assert_eq!(rom.read_rom(3).unwrap(), 0x78);
|
|
}
|
|
|
|
#[test]
|
|
fn test_intel4001_io_modes() {
|
|
let rom = Intel4001::new("ROM_4001".to_string());
|
|
|
|
// Test that I/O mode starts as Input (after reset)
|
|
// Note: IoMode is now internal and not exposed via public API
|
|
// I/O direction is determined by CPU instructions (WRM/RDM)
|
|
assert_eq!(rom.name(), "ROM_4001");
|
|
}
|
|
|
|
#[test]
|
|
fn test_intel4001_latches() {
|
|
let rom = Intel4001::new("ROM_4001".to_string());
|
|
|
|
// Initial state
|
|
assert_eq!(rom.get_output_latch(), 0);
|
|
assert_eq!(rom.get_input_latch(), 0);
|
|
|
|
// These would be set during actual I/O operations
|
|
// The test verifies the latch structures exist
|
|
}
|
|
|
|
#[test]
|
|
fn test_io_mode_display() {
|
|
assert_eq!(IoMode::Input.to_string(), "Input");
|
|
assert_eq!(IoMode::Output.to_string(), "Output");
|
|
assert_eq!(IoMode::Bidirectional.to_string(), "Bidirectional");
|
|
}
|
|
|
|
#[test]
|
|
fn test_configurable_access_time() {
|
|
let mut rom = Intel4001::new("ROM_4001".to_string());
|
|
|
|
// Test default access time
|
|
assert_eq!(rom.get_access_time(), 500);
|
|
|
|
// Test setting custom access time
|
|
rom.set_access_time(100);
|
|
assert_eq!(rom.get_access_time(), 100);
|
|
|
|
// Test constructor with custom access time
|
|
let fast_rom = Intel4001::new_with_access_time("FAST_ROM".to_string(), 1);
|
|
assert_eq!(fast_rom.get_access_time(), 1);
|
|
assert_eq!(fast_rom.name(), "FAST_ROM");
|
|
}
|
|
|
|
#[test]
|
|
fn test_basic_memory_read() {
|
|
let mut rom = Intel4001::new_with_access_time("ROM_4001".to_string(), 1);
|
|
|
|
// Load test data
|
|
let test_data = vec![0x12, 0x34];
|
|
rom.load_rom_data(test_data, 0).unwrap();
|
|
|
|
// Test direct memory read (bypassing pin simulation)
|
|
assert_eq!(rom.read_rom(0x00).unwrap(), 0x12);
|
|
assert_eq!(rom.read_rom(0x01).unwrap(), 0x34);
|
|
}
|
|
|
|
#[test]
|
|
fn test_address_latching() {
|
|
// Note: This test sets Φ1 and Φ2 directly for unit validation.
|
|
// For system integration testing, consider using a proper clock generator
|
|
// that drives Φ1/Φ2 continuously through realistic multiple cycles.
|
|
let mut rom = Intel4001::new_with_access_time("ROM_4001".to_string(), 1);
|
|
|
|
// Load test data
|
|
let test_data = vec![0x12];
|
|
rom.load_rom_data(test_data, 0).unwrap();
|
|
|
|
// Get pin references
|
|
let sync_pin = rom.get_pin("SYNC").unwrap();
|
|
let cm_pin = rom.get_pin("CM").unwrap();
|
|
let ci_pin = rom.get_pin("CI").unwrap();
|
|
let phi1_pin = rom.get_pin("PHI1").unwrap();
|
|
let phi2_pin = rom.get_pin("PHI2").unwrap();
|
|
let d0_pin = rom.get_pin("D0").unwrap();
|
|
let d1_pin = rom.get_pin("D1").unwrap();
|
|
let d2_pin = rom.get_pin("D2").unwrap();
|
|
let d3_pin = rom.get_pin("D3").unwrap();
|
|
|
|
// Initialize clock pins to Low to ensure proper edge detection
|
|
{
|
|
let mut phi1_guard = phi1_pin.lock().unwrap();
|
|
let mut phi2_guard = phi2_pin.lock().unwrap();
|
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
|
|
// Set up memory read operation: SYNC=1, CM=1, CI=0 (memory access)
|
|
// These must remain set throughout the entire memory operation
|
|
{
|
|
let mut sync_guard = sync_pin.lock().unwrap();
|
|
let mut cm_guard = cm_pin.lock().unwrap();
|
|
let mut ci_guard = ci_pin.lock().unwrap();
|
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access
|
|
}
|
|
|
|
// 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); // Bit 0 = 0
|
|
d1_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 1 = 0
|
|
d2_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 2 = 0
|
|
d3_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 3 = 0
|
|
}
|
|
|
|
// --- High nibble ---
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::High);
|
|
rom.update(); // rising edge -> latch high nibble
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::Low);
|
|
rom.update(); // falling edge
|
|
|
|
// Should have transitioned to AddressPhase
|
|
assert_eq!(rom.memory_state, MemoryState::AddressPhase);
|
|
|
|
// Check that high nibble was latched
|
|
assert_eq!(rom.address_high_nibble, Some(0x0));
|
|
|
|
// Set address low 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".into()), PinValue::Low); // Bit 0 = 0
|
|
d1_guard.set_driver(Some("TEST".into()), PinValue::Low); // Bit 1 = 0
|
|
d2_guard.set_driver(Some("TEST".into()), PinValue::Low); // Bit 2 = 0
|
|
d3_guard.set_driver(Some("TEST".into()), PinValue::Low); // Bit 3 = 0
|
|
}
|
|
|
|
// --- Low nibble ---
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::High);
|
|
rom.update(); // rising edge -> latch low nibble
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::Low);
|
|
rom.update(); // falling edge
|
|
|
|
// Should have assembled full address and transitioned to WaitLatency
|
|
// Note: nibbles are cleared after assembly, so check last_address instead
|
|
assert_eq!(rom.last_address, 0x00);
|
|
assert_eq!(rom.full_address_ready, true);
|
|
assert_eq!(rom.memory_state, MemoryState::WaitLatency);
|
|
|
|
// Advance simulated time to exceed access_time (1ns)
|
|
std::thread::sleep(Duration::from_nanos(2));
|
|
}
|
|
|
|
#[test]
|
|
fn test_memory_operation_start() {
|
|
let mut rom = Intel4001::new_with_access_time("ROM_4001".to_string(), 1);
|
|
|
|
// Initially should be in idle state
|
|
assert_eq!(rom.memory_state, MemoryState::Idle);
|
|
|
|
// Get pin references
|
|
let sync_pin = rom.get_pin("SYNC").unwrap();
|
|
let cm_pin = rom.get_pin("CM").unwrap();
|
|
let ci_pin = rom.get_pin("CI").unwrap();
|
|
let phi1_pin = rom.get_pin("PHI1").unwrap();
|
|
|
|
// Set up memory read operation: SYNC=1, CM=1, CI=0 (memory access)
|
|
{
|
|
let mut sync_guard = sync_pin.lock().unwrap();
|
|
let mut cm_guard = cm_pin.lock().unwrap();
|
|
let mut ci_guard = ci_pin.lock().unwrap();
|
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access
|
|
}
|
|
|
|
// Set Φ1 high
|
|
{
|
|
let mut phi1_guard = phi1_pin.lock().unwrap();
|
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
}
|
|
|
|
// Update to process Φ1 rising edge
|
|
rom.update();
|
|
|
|
// Should have started memory operation and transitioned to AddressPhase
|
|
assert_eq!(rom.memory_state, MemoryState::AddressPhase);
|
|
println!("Memory state after Φ1 rising: {:?}", rom.memory_state);
|
|
}
|
|
|
|
#[test]
|
|
fn test_simple_data_driving() {
|
|
// Note: This test manually sequences through clock phases for unit validation.
|
|
// For system integration, use continuous clock generator for realistic timing.
|
|
let mut rom = Intel4001::new_with_access_time("ROM_4001".to_string(), 1);
|
|
|
|
// Load test data
|
|
let test_data = vec![0x12];
|
|
rom.load_rom_data(test_data, 0).unwrap();
|
|
|
|
// Initial state debug
|
|
rom.debug_state_transitions("INITIAL");
|
|
|
|
// Get pin references
|
|
let sync_pin = rom.get_pin("SYNC").unwrap();
|
|
let cm_pin = rom.get_pin("CM").unwrap();
|
|
let ci_pin = rom.get_pin("CI").unwrap();
|
|
let phi1_pin = rom.get_pin("PHI1").unwrap();
|
|
let phi2_pin = rom.get_pin("PHI2").unwrap();
|
|
let d0_pin = rom.get_pin("D0").unwrap();
|
|
let d1_pin = rom.get_pin("D1").unwrap();
|
|
let d2_pin = rom.get_pin("D2").unwrap();
|
|
let d3_pin = rom.get_pin("D3").unwrap();
|
|
|
|
// Set up memory read operation: SYNC=1, CM=1, CI=0 (memory access)
|
|
{
|
|
let mut sync_guard = sync_pin.lock().unwrap();
|
|
let mut cm_guard = cm_pin.lock().unwrap();
|
|
let mut ci_guard = ci_pin.lock().unwrap();
|
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access
|
|
}
|
|
|
|
// Phase 1: Address phase - latch high nibble (0x0)
|
|
{
|
|
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); // Bit 0 = 0
|
|
d1_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 1 = 0
|
|
d2_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 2 = 0
|
|
d3_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 3 = 0
|
|
}
|
|
|
|
// --- High nibble ---
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::High);
|
|
rom.update(); // rising edge -> latch high nibble
|
|
rom.debug_state_transitions("AFTER_HIGH_NIBBLE_RISING");
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::Low);
|
|
rom.update(); // falling edge
|
|
rom.debug_state_transitions("AFTER_HIGH_NIBBLE_FALLING");
|
|
|
|
// Phase 2: Address phase - latch low nibble (0x0)
|
|
// Re-assert control pins to ensure they're maintained during entire operation
|
|
{
|
|
let mut sync_guard = sync_pin.lock().unwrap();
|
|
let mut cm_guard = cm_pin.lock().unwrap();
|
|
let mut ci_guard = ci_pin.lock().unwrap();
|
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access
|
|
}
|
|
// Tri-state data bus to let ROM take control
|
|
{
|
|
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::HighZ);
|
|
d1_guard.set_driver(Some("TEST".into()), PinValue::HighZ);
|
|
d2_guard.set_driver(Some("TEST".into()), PinValue::HighZ);
|
|
d3_guard.set_driver(Some("TEST".into()), PinValue::HighZ);
|
|
}
|
|
|
|
// --- Low nibble ---
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::High);
|
|
rom.update(); // rising edge -> latch low nibble
|
|
rom.debug_state_transitions("AFTER_LOW_NIBBLE_RISING");
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::Low);
|
|
rom.update(); // falling edge
|
|
rom.debug_state_transitions("AFTER_LOW_NIBBLE_FALLING");
|
|
|
|
// Advance simulated time to exceed access_time (1ns)
|
|
rom.debug_state_transitions("BEFORE_SLEEP");
|
|
std::thread::sleep(Duration::from_nanos(2));
|
|
rom.debug_state_transitions("AFTER_SLEEP");
|
|
|
|
// --- Data phase ---
|
|
// First set Φ2 to Low, then High to ensure proper edge detection
|
|
phi2_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::Low);
|
|
rom.update(); // ensure low state
|
|
phi2_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::High);
|
|
rom.update(); // rising edge -> drive data
|
|
rom.debug_state_transitions("AFTER_PHI2_RISING");
|
|
|
|
// After sleep, latency should have elapsed, so should transition to DriveData on Φ2 rising
|
|
// The state should transition to DriveData during the Φ2 rising edge
|
|
assert_eq!(rom.memory_state, MemoryState::DriveData);
|
|
println!("After Φ2 high - State: {:?}", rom.memory_state);
|
|
|
|
// Debug: Check what data the ROM thinks it should drive
|
|
let address = rom.last_address;
|
|
let expected_data = rom.memory[address as usize];
|
|
println!("DEBUG: ROM address 0x{:x}, data at address: 0x{:x}", address, expected_data);
|
|
|
|
// Debug: Check the bit extraction logic
|
|
let bit0 = (expected_data >> 0) & 1;
|
|
let bit1 = (expected_data >> 1) & 1;
|
|
let bit2 = (expected_data >> 2) & 1;
|
|
let bit3 = (expected_data >> 3) & 1;
|
|
println!("DEBUG: Bit extraction - bit0: {}, bit1: {}, bit2: {}, bit3: {}", bit0, bit1, bit2, bit3);
|
|
|
|
// Debug: Check actual pin states and settlement status
|
|
let d0_actual = d0_pin.lock().unwrap().read();
|
|
let d1_actual = d1_pin.lock().unwrap().read();
|
|
let d2_actual = d2_pin.lock().unwrap().read();
|
|
let d3_actual = d3_pin.lock().unwrap().read();
|
|
|
|
let d0_immediate = d0_pin.lock().unwrap().read_immediate();
|
|
let d1_immediate = d1_pin.lock().unwrap().read_immediate();
|
|
let d2_immediate = d2_pin.lock().unwrap().read_immediate();
|
|
let d3_immediate = d3_pin.lock().unwrap().read_immediate();
|
|
|
|
let d0_settled = d0_pin.lock().unwrap().is_settled();
|
|
let d1_settled = d1_pin.lock().unwrap().is_settled();
|
|
let d2_settled = d2_pin.lock().unwrap().is_settled();
|
|
let d3_settled = d3_pin.lock().unwrap().is_settled();
|
|
|
|
println!("DEBUG: D0 actual: {:?} (bit 0: {}), immediate: {:?}, settled: {}", d0_actual, bit0, d0_immediate, d0_settled);
|
|
println!("DEBUG: D1 actual: {:?} (bit 1: {}), immediate: {:?}, settled: {}", d1_actual, bit1, d1_immediate, d1_settled);
|
|
println!("DEBUG: D2 actual: {:?} (bit 2: {}), immediate: {:?}, settled: {}", d2_actual, bit2, d2_immediate, d2_settled);
|
|
println!("DEBUG: D3 actual: {:?} (bit 3: {}), immediate: {:?}, settled: {}", d3_actual, bit3, d3_immediate, d3_settled);
|
|
|
|
// Debug: Check actual pin states immediately after data driving
|
|
let d0_actual = d0_pin.lock().unwrap().read();
|
|
let d1_actual = d1_pin.lock().unwrap().read();
|
|
let d2_actual = d2_pin.lock().unwrap().read();
|
|
let d3_actual = d3_pin.lock().unwrap().read();
|
|
|
|
println!("DEBUG: After data driving - D0: {:?}, D1: {:?}, D2: {:?}, D3: {:?}", d0_actual, d1_actual, d2_actual, d3_actual);
|
|
|
|
// Debug: Check what the ROM thinks it should be driving
|
|
let expected_data = 0x12;
|
|
let bit0 = (expected_data >> 0) & 1;
|
|
let bit1 = (expected_data >> 1) & 1;
|
|
let bit2 = (expected_data >> 2) & 1;
|
|
let bit3 = (expected_data >> 3) & 1;
|
|
println!("DEBUG: Expected bits - bit0: {}, bit1: {}, bit2: {}, bit3: {}", bit0, bit1, bit2, bit3);
|
|
|
|
// Verify data is driven on bus while Φ2 is high: 0x12 = 0001 0010
|
|
// 0x12 = 18 decimal = 0001 0010 binary
|
|
// For 4-bit data bus: D3=0, D2=0, D1=1, D0=0
|
|
|
|
// Small delay to allow pin state to settle
|
|
std::thread::sleep(Duration::from_micros(1));
|
|
|
|
assert_eq!(d0_pin.lock().unwrap().read(), PinValue::Low); // Bit 0 = 0
|
|
assert_eq!(d1_pin.lock().unwrap().read(), PinValue::High); // Bit 1 = 1
|
|
assert_eq!(d2_pin.lock().unwrap().read(), PinValue::Low); // Bit 2 = 0
|
|
assert_eq!(d3_pin.lock().unwrap().read(), PinValue::Low); // Bit 3 = 0 (NOT 1!)
|
|
|
|
phi2_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::Low);
|
|
rom.update(); // falling edge -> tri-state
|
|
}
|
|
|
|
#[test]
|
|
fn test_memory_operation_start_detection() {
|
|
let mut rom = Intel4001::new_with_access_time("ROM_4001".to_string(), 1);
|
|
|
|
// Load test data
|
|
let test_data = vec![0x12];
|
|
rom.load_rom_data(test_data, 0).unwrap();
|
|
|
|
// Get pin references
|
|
let sync_pin = rom.get_pin("SYNC").unwrap();
|
|
let cm_pin = rom.get_pin("CM").unwrap();
|
|
let ci_pin = rom.get_pin("CI").unwrap();
|
|
let phi1_pin = rom.get_pin("PHI1").unwrap();
|
|
let phi2_pin = rom.get_pin("PHI2").unwrap();
|
|
let d0_pin = rom.get_pin("D0").unwrap();
|
|
let d1_pin = rom.get_pin("D1").unwrap();
|
|
let d2_pin = rom.get_pin("D2").unwrap();
|
|
let d3_pin = rom.get_pin("D3").unwrap();
|
|
|
|
// Initially, memory should be in idle state
|
|
assert_eq!(rom.memory_state, MemoryState::Idle);
|
|
|
|
// Set up memory read operation: SYNC=1, CM=1, CI=0 (memory access)
|
|
{
|
|
let mut sync_guard = sync_pin.lock().unwrap();
|
|
let mut cm_guard = cm_pin.lock().unwrap();
|
|
let mut ci_guard = ci_pin.lock().unwrap();
|
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access
|
|
}
|
|
|
|
// Tri-state data bus to let ROM take control
|
|
// Note: Don't clear ROM's driver - let ROM set its own driver when it drives data
|
|
// The ROM uses driver name format "{}_DATA" (e.g., "ROM_4001_DATA")
|
|
|
|
// Set Φ1 high, Φ2 low - should start memory operation
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::High);
|
|
phi2_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::Low);
|
|
|
|
// Update to process Φ1 rising edge
|
|
rom.update();
|
|
|
|
// Should have transitioned to AddressPhase
|
|
assert_eq!(rom.memory_state, MemoryState::AddressPhase);
|
|
|
|
// Set address on data bus for latching (CPU drives during address phase)
|
|
{
|
|
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("CPU".to_string()), PinValue::Low);
|
|
d1_guard.set_driver(Some("CPU".to_string()), PinValue::Low);
|
|
d2_guard.set_driver(Some("CPU".to_string()), PinValue::Low);
|
|
d3_guard.set_driver(Some("CPU".to_string()), PinValue::Low);
|
|
}
|
|
|
|
// --- High nibble ---
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::Low);
|
|
rom.update(); // falling edge
|
|
|
|
// --- Low nibble ---
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::High);
|
|
rom.update(); // rising edge -> latch low nibble
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::Low);
|
|
rom.update(); // falling edge
|
|
|
|
// Advance simulated time to exceed access_time (1ns)
|
|
std::thread::sleep(Duration::from_nanos(2));
|
|
|
|
// --- Data phase ---
|
|
phi2_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::High);
|
|
rom.update(); // rising edge -> drive data
|
|
|
|
// Assert DriveData state while Φ2 is high
|
|
assert_eq!(rom.memory_state, MemoryState::DriveData);
|
|
|
|
// Verify data is driven on bus while Φ2 is high: 0x12 = 0001 0010
|
|
// 0x12 = 18 decimal = 0001 0010 binary
|
|
// For 4-bit data bus: D3=0, D2=0, D1=1, D0=0
|
|
assert_eq!(d0_pin.lock().unwrap().read(), PinValue::Low); // Bit 0 = 0
|
|
assert_eq!(d1_pin.lock().unwrap().read(), PinValue::High); // Bit 1 = 1
|
|
assert_eq!(d2_pin.lock().unwrap().read(), PinValue::Low); // Bit 2 = 0
|
|
assert_eq!(d3_pin.lock().unwrap().read(), PinValue::Low); // Bit 3 = 0 (NOT 1!)
|
|
|
|
phi2_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::Low);
|
|
rom.update(); // falling edge -> tri-state
|
|
}
|
|
|
|
#[test]
|
|
fn test_full_address_fetch_cycle_integration() {
|
|
// Note: This integration test manually steps through clock phases.
|
|
// For full system testing, implement continuous clock generator
|
|
// that maintains proper Φ1/Φ2 timing relationships.
|
|
let mut rom = Intel4001::new_with_access_time("ROM_4001".to_string(), 1);
|
|
|
|
// Load test data: address 0x00 = 0x12, address 0x01 = 0x34
|
|
let test_data = vec![0x12, 0x34, 0x56, 0x78];
|
|
rom.load_rom_data(test_data, 0).unwrap();
|
|
|
|
// Get pin references for simulation
|
|
let sync_pin = rom.get_pin("SYNC").unwrap();
|
|
let cm_pin = rom.get_pin("CM").unwrap();
|
|
let ci_pin = rom.get_pin("CI").unwrap();
|
|
let phi1_pin = rom.get_pin("PHI1").unwrap();
|
|
let phi2_pin = rom.get_pin("PHI2").unwrap();
|
|
let d0_pin = rom.get_pin("D0").unwrap();
|
|
let d1_pin = rom.get_pin("D1").unwrap();
|
|
let d2_pin = rom.get_pin("D2").unwrap();
|
|
let d3_pin = rom.get_pin("D3").unwrap();
|
|
|
|
// Set up memory read operation: SYNC=1, CM=1, CI=0 (memory access, not I/O)
|
|
{
|
|
let mut sync_guard = sync_pin.lock().unwrap();
|
|
let mut cm_guard = cm_pin.lock().unwrap();
|
|
let mut ci_guard = ci_pin.lock().unwrap();
|
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access, not I/O
|
|
}
|
|
|
|
// Test complete cycle for address 0x00
|
|
// Step 1: Clear CPU driver to let ROM take control
|
|
{
|
|
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("CPU".to_string()), PinValue::HighZ);
|
|
d1_guard.set_driver(Some("CPU".to_string()), PinValue::HighZ);
|
|
d2_guard.set_driver(Some("CPU".to_string()), PinValue::HighZ);
|
|
d3_guard.set_driver(Some("CPU".to_string()), PinValue::HighZ);
|
|
}
|
|
|
|
// Step 2: High nibble
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::Low);
|
|
rom.update(); // falling edge
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::High);
|
|
rom.update(); // rising edge for high nibble
|
|
|
|
// Step 3: Clear CPU driver to let ROM take control
|
|
{
|
|
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("CPU".to_string()), PinValue::HighZ);
|
|
d1_guard.set_driver(Some("CPU".to_string()), PinValue::HighZ);
|
|
d2_guard.set_driver(Some("CPU".to_string()), PinValue::HighZ);
|
|
d3_guard.set_driver(Some("CPU".to_string()), PinValue::HighZ);
|
|
}
|
|
|
|
// Step 4: Low nibble
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::Low);
|
|
rom.update(); // falling edge
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::High);
|
|
rom.update(); // rising edge for low nibble
|
|
|
|
// Advance simulated time to exceed access_time (1ns)
|
|
std::thread::sleep(Duration::from_nanos(2));
|
|
|
|
// Data phase
|
|
phi2_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::High);
|
|
rom.update(); // rising edge -> drive data
|
|
|
|
// Step 5: Verify data is driven on bus while Φ2 is high: 0x12 = 0001 0010
|
|
// 0x12 = 18 decimal = 0001 0010 binary
|
|
// For 4-bit data bus: D3=0, D2=0, D1=1, D0=0
|
|
assert_eq!(d0_pin.lock().unwrap().read(), PinValue::Low); // Bit 0 = 0
|
|
assert_eq!(d1_pin.lock().unwrap().read(), PinValue::High); // Bit 1 = 1
|
|
assert_eq!(d2_pin.lock().unwrap().read(), PinValue::Low); // Bit 2 = 0
|
|
assert_eq!(d3_pin.lock().unwrap().read(), PinValue::Low); // Bit 3 = 0 (NOT 1!)
|
|
|
|
phi2_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::Low);
|
|
rom.update(); // falling edge -> tri-state
|
|
|
|
// Test complete cycle for address 0x01
|
|
// Reset memory state first
|
|
rom.return_to_idle();
|
|
|
|
// Step 1: Clear CPU driver to let ROM take control
|
|
{
|
|
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("CPU".to_string()), PinValue::HighZ);
|
|
d1_guard.set_driver(Some("CPU".to_string()), PinValue::HighZ);
|
|
d2_guard.set_driver(Some("CPU".to_string()), PinValue::HighZ);
|
|
d3_guard.set_driver(Some("CPU".to_string()), PinValue::HighZ);
|
|
}
|
|
|
|
// Step 2: High nibble
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::Low);
|
|
rom.update(); // falling edge
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::High);
|
|
rom.update(); // rising edge for high nibble
|
|
|
|
// Step 3: Clear CPU driver to let ROM take control
|
|
{
|
|
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("CPU".to_string()), PinValue::HighZ);
|
|
d1_guard.set_driver(Some("CPU".to_string()), PinValue::HighZ);
|
|
d2_guard.set_driver(Some("CPU".to_string()), PinValue::HighZ);
|
|
d3_guard.set_driver(Some("CPU".to_string()), PinValue::HighZ);
|
|
}
|
|
|
|
// Step 4: Low nibble
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::Low);
|
|
rom.update(); // falling edge
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::High);
|
|
rom.update(); // rising edge for low nibble
|
|
|
|
// Advance simulated time to exceed access_time (1ns)
|
|
std::thread::sleep(Duration::from_nanos(2));
|
|
|
|
// Data phase
|
|
phi2_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::High);
|
|
rom.update(); // rising edge -> drive data
|
|
|
|
// Step 5: Verify data is driven on bus while Φ2 is high: 0x34 = 0011 0100
|
|
// 0x34 = 52 decimal = 0011 0100 binary
|
|
// For 4-bit data bus: D3=0, D2=0, D1=1, D0=1
|
|
assert_eq!(d0_pin.lock().unwrap().read(), PinValue::Low); // Bit 0 = 0
|
|
assert_eq!(d1_pin.lock().unwrap().read(), PinValue::Low); // Bit 1 = 0
|
|
assert_eq!(d2_pin.lock().unwrap().read(), PinValue::High); // Bit 2 = 1
|
|
assert_eq!(d3_pin.lock().unwrap().read(), PinValue::Low); // Bit 3 = 0 (NOT 1!)
|
|
|
|
phi2_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::Low);
|
|
rom.update(); // falling edge -> tri-state
|
|
}
|
|
|
|
#[test]
|
|
fn test_clock_driven_memory_fetch() {
|
|
let mut rom = Intel4001::new_with_access_time("ROM_4001".to_string(), 1); // 1ns for fast testing
|
|
|
|
// Load test data: address 0x01 should contain 0x34
|
|
let test_data = vec![0x12, 0x34, 0x56, 0x78];
|
|
rom.load_rom_data(test_data, 0).unwrap();
|
|
|
|
// Get pin references for simulation
|
|
let sync_pin = rom.get_pin("SYNC").unwrap();
|
|
let cm_pin = rom.get_pin("CM").unwrap();
|
|
let ci_pin = rom.get_pin("CI").unwrap();
|
|
let d0_pin = rom.get_pin("D0").unwrap();
|
|
let d1_pin = rom.get_pin("D1").unwrap();
|
|
let d2_pin = rom.get_pin("D2").unwrap();
|
|
let d3_pin = rom.get_pin("D3").unwrap();
|
|
|
|
// Set up memory read operation: SYNC=1, chip_select=1, io_select=0 (memory access)
|
|
{
|
|
let mut sync_guard = sync_pin.lock().unwrap();
|
|
let mut cm_guard = cm_pin.lock().unwrap();
|
|
let mut ci_guard = ci_pin.lock().unwrap();
|
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access, not I/O
|
|
}
|
|
|
|
// Test that memory state machine works correctly
|
|
// The state machine should handle the two-phase addressing properly
|
|
rom.update();
|
|
|
|
// Verify that the ROM correctly read the test data
|
|
assert_eq!(rom.read_rom(0).unwrap(), 0x12);
|
|
assert_eq!(rom.read_rom(1).unwrap(), 0x34);
|
|
assert_eq!(rom.read_rom(2).unwrap(), 0x56);
|
|
assert_eq!(rom.read_rom(3).unwrap(), 0x78);
|
|
}
|
|
|
|
#[test]
|
|
fn test_reset_behavior() {
|
|
let mut rom = Intel4001::new_with_access_time("ROM_4001".to_string(), 1); // 1ns for fast testing
|
|
|
|
// Load test data
|
|
let test_data = vec![0x12, 0x34, 0x56, 0x78];
|
|
rom.load_rom_data(test_data, 0).unwrap();
|
|
|
|
// Set up some state first
|
|
let sync_pin = rom.get_pin("SYNC").unwrap();
|
|
let cm_pin = rom.get_pin("CM").unwrap();
|
|
let ci_pin = rom.get_pin("CI").unwrap();
|
|
let reset_pin = rom.get_pin("RESET").unwrap();
|
|
|
|
// Set memory operation active
|
|
{
|
|
let mut sync_guard = sync_pin.lock().unwrap();
|
|
let mut cm_guard = cm_pin.lock().unwrap();
|
|
let mut ci_guard = ci_pin.lock().unwrap();
|
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access, not I/O
|
|
}
|
|
|
|
// Trigger some state changes
|
|
rom.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
|
|
rom.update();
|
|
|
|
// Verify all state is cleared
|
|
assert_eq!(rom.get_output_latch(), 0);
|
|
assert_eq!(rom.get_input_latch(), 0);
|
|
|
|
// Verify data bus is tri-stated
|
|
let d0_pin = rom.get_pin("D0").unwrap();
|
|
let d1_pin = rom.get_pin("D1").unwrap();
|
|
let d2_pin = rom.get_pin("D2").unwrap();
|
|
let d3_pin = rom.get_pin("D3").unwrap();
|
|
|
|
// After reset, all pins should be HighZ (tri-stated)
|
|
assert_eq!(d0_pin.lock().unwrap().read(), PinValue::HighZ);
|
|
assert_eq!(d1_pin.lock().unwrap().read(), PinValue::HighZ);
|
|
assert_eq!(d2_pin.lock().unwrap().read(), PinValue::HighZ);
|
|
assert_eq!(d3_pin.lock().unwrap().read(), PinValue::HighZ);
|
|
}
|
|
|
|
#[test]
|
|
fn test_tri_state_behavior() {
|
|
let mut rom = Intel4001::new_with_access_time("ROM_4001".to_string(), 1); // 1ns for fast testing
|
|
|
|
// Load test data
|
|
let test_data = vec![0x12, 0x34, 0x56, 0x78];
|
|
rom.load_rom_data(test_data, 0).unwrap();
|
|
|
|
let d0_pin = rom.get_pin("D0").unwrap();
|
|
let d1_pin = rom.get_pin("D1").unwrap();
|
|
let d2_pin = rom.get_pin("D2").unwrap();
|
|
let d3_pin = rom.get_pin("D3").unwrap();
|
|
|
|
// Initially, no operation - should be tri-stated
|
|
assert_eq!(d0_pin.lock().unwrap().read(), PinValue::HighZ);
|
|
assert_eq!(d1_pin.lock().unwrap().read(), PinValue::HighZ);
|
|
assert_eq!(d2_pin.lock().unwrap().read(), PinValue::HighZ);
|
|
assert_eq!(d3_pin.lock().unwrap().read(), PinValue::HighZ);
|
|
|
|
// Set up memory operation but don't complete address phase
|
|
let sync_pin = rom.get_pin("SYNC").unwrap();
|
|
let cm_pin = rom.get_pin("CM").unwrap();
|
|
let ci_pin = rom.get_pin("CI").unwrap();
|
|
|
|
let mut sync_guard = sync_pin.lock().unwrap();
|
|
let mut cm_guard = cm_pin.lock().unwrap();
|
|
let mut ci_guard = ci_pin.lock().unwrap();
|
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access, not I/O
|
|
|
|
// Trigger update - should start address phase but still tri-state
|
|
rom.update();
|
|
|
|
// Should still be tri-stated during address phase
|
|
assert_eq!(d0_pin.lock().unwrap().read(), PinValue::HighZ);
|
|
assert_eq!(d1_pin.lock().unwrap().read(), PinValue::HighZ);
|
|
assert_eq!(d2_pin.lock().unwrap().read(), PinValue::HighZ);
|
|
assert_eq!(d3_pin.lock().unwrap().read(), PinValue::HighZ);
|
|
|
|
// Complete address phase but don't wait for latency
|
|
// Should still be tri-stated
|
|
rom.update();
|
|
|
|
// Should still be tri-stated (waiting for latency)
|
|
assert_eq!(d0_pin.lock().unwrap().read(), PinValue::HighZ);
|
|
assert_eq!(d1_pin.lock().unwrap().read(), PinValue::HighZ);
|
|
assert_eq!(d2_pin.lock().unwrap().read(), PinValue::HighZ);
|
|
assert_eq!(d3_pin.lock().unwrap().read(), PinValue::HighZ);
|
|
}
|
|
|
|
#[test]
|
|
fn test_clock_generator_integration() {
|
|
// Integration test with simulated clock generator stepping through multiple cycles
|
|
// This tests more realistic timing scenarios compared to direct Φ1/Φ2 manipulation
|
|
let mut rom = Intel4001::new_with_access_time("ROM_4001".to_string(), 1);
|
|
|
|
// Load test data: address 0x00 = 0x12, address 0x01 = 0x34
|
|
let test_data = vec![0x12, 0x34];
|
|
rom.load_rom_data(test_data, 0).unwrap();
|
|
|
|
// Get pin references
|
|
let sync_pin = rom.get_pin("SYNC").unwrap();
|
|
let cm_pin = rom.get_pin("CM").unwrap();
|
|
let ci_pin = rom.get_pin("CI").unwrap();
|
|
let phi1_pin = rom.get_pin("PHI1").unwrap();
|
|
let phi2_pin = rom.get_pin("PHI2").unwrap();
|
|
let d0_pin = rom.get_pin("D0").unwrap();
|
|
let d1_pin = rom.get_pin("D1").unwrap();
|
|
let d2_pin = rom.get_pin("D2").unwrap();
|
|
let d3_pin = rom.get_pin("D3").unwrap();
|
|
|
|
// Set up memory read operation: SYNC=1, CM=1, CI=0 (memory access)
|
|
{
|
|
let mut sync_guard = sync_pin.lock().unwrap();
|
|
let mut cm_guard = cm_pin.lock().unwrap();
|
|
let mut ci_guard = ci_pin.lock().unwrap();
|
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Memory access
|
|
}
|
|
|
|
// Simulate clock generator: step through multiple Φ1/Φ2 cycles
|
|
// Cycle 1: Address phase for high nibble (0x0)
|
|
{
|
|
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::HighZ);
|
|
d1_guard.set_driver(Some("TEST".to_string()), PinValue::HighZ);
|
|
d2_guard.set_driver(Some("TEST".to_string()), PinValue::HighZ);
|
|
d3_guard.set_driver(Some("TEST".to_string()), PinValue::HighZ);
|
|
}
|
|
|
|
// High nibble
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::Low);
|
|
rom.update(); // falling edge
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::High);
|
|
rom.update(); // rising edge for high nibble
|
|
|
|
// Cycle 2: Address phase for low nibble (0x0)
|
|
{
|
|
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::HighZ);
|
|
d1_guard.set_driver(Some("TEST".into()), PinValue::HighZ);
|
|
d2_guard.set_driver(Some("TEST".into()), PinValue::HighZ);
|
|
d3_guard.set_driver(Some("TEST".into()), PinValue::HighZ);
|
|
}
|
|
|
|
// Low nibble
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::Low);
|
|
rom.update(); // falling edge
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::High);
|
|
rom.update(); // rising edge for low nibble
|
|
|
|
// Advance simulated time to exceed access_time (1ns)
|
|
std::thread::sleep(Duration::from_nanos(2));
|
|
|
|
// Data phase
|
|
phi2_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::High);
|
|
rom.update(); // rising edge -> drive data
|
|
|
|
// Should have data 0x12 on bus while Φ2 is high: 0001 0010
|
|
// 0x12 = 18 decimal = 0001 0010 binary
|
|
// For 4-bit data bus: D3=0, D2=0, D1=1, D0=0
|
|
assert_eq!(d0_pin.lock().unwrap().read(), PinValue::Low); // Bit 0 = 0
|
|
assert_eq!(d1_pin.lock().unwrap().read(), PinValue::High); // Bit 1 = 1
|
|
assert_eq!(d2_pin.lock().unwrap().read(), PinValue::Low); // Bit 2 = 0
|
|
assert_eq!(d3_pin.lock().unwrap().read(), PinValue::Low); // Bit 3 = 0 (NOT 1!)
|
|
|
|
phi2_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::Low);
|
|
rom.update(); // falling edge -> tri-state
|
|
|
|
// Cycle 3: Next memory operation for address 0x01
|
|
rom.return_to_idle();
|
|
|
|
// Clear CPU driver to let ROM take control for address 0x01
|
|
{
|
|
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("CPU".to_string()), PinValue::HighZ);
|
|
d1_guard.set_driver(Some("CPU".to_string()), PinValue::HighZ);
|
|
d2_guard.set_driver(Some("CPU".to_string()), PinValue::HighZ);
|
|
d3_guard.set_driver(Some("CPU".to_string()), PinValue::HighZ);
|
|
}
|
|
|
|
// High nibble
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::Low);
|
|
rom.update(); // falling edge
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::High);
|
|
rom.update(); // rising edge for high nibble
|
|
|
|
// Clear CPU driver to let ROM take control for low 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("CPU".to_string()), PinValue::HighZ);
|
|
d1_guard.set_driver(Some("CPU".to_string()), PinValue::HighZ);
|
|
d2_guard.set_driver(Some("CPU".to_string()), PinValue::HighZ);
|
|
d3_guard.set_driver(Some("CPU".to_string()), PinValue::HighZ);
|
|
}
|
|
|
|
// Low nibble
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::Low);
|
|
rom.update(); // falling edge
|
|
phi1_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::High);
|
|
rom.update(); // rising edge for low nibble
|
|
|
|
// Advance simulated time to exceed access_time (1ns)
|
|
std::thread::sleep(Duration::from_nanos(2));
|
|
|
|
// Data phase
|
|
phi2_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::High);
|
|
rom.update(); // rising edge -> drive data
|
|
|
|
// Should have data 0x34 on bus while Φ2 is high: 0011 0100
|
|
// 0x34 = 52 decimal = 0011 0100 binary
|
|
// For 4-bit data bus: D3=0, D2=0, D1=1, D0=1
|
|
assert_eq!(d0_pin.lock().unwrap().read(), PinValue::Low); // Bit 0 = 0
|
|
assert_eq!(d1_pin.lock().unwrap().read(), PinValue::Low); // Bit 1 = 0
|
|
assert_eq!(d2_pin.lock().unwrap().read(), PinValue::High); // Bit 2 = 1
|
|
assert_eq!(d3_pin.lock().unwrap().read(), PinValue::Low); // Bit 3 = 0 (NOT 1!)
|
|
|
|
phi2_pin
|
|
.lock()
|
|
.unwrap()
|
|
.set_driver(Some("TEST".into()), PinValue::Low);
|
|
rom.update(); // falling edge -> tri-state
|
|
}
|
|
}
|