Added more tests
Extracted common 400x behavior like timing and latching into common module Added maxed MCS-4
This commit is contained in:
parent
dd93e33087
commit
c039e9069a
25 changed files with 5694 additions and 494 deletions
416
src/components/common/intel_400x.rs
Normal file
416
src/components/common/intel_400x.rs
Normal file
|
|
@ -0,0 +1,416 @@
|
|||
//! Common functionality for Intel 400x series chips
|
||||
//!
|
||||
//! This module provides shared timing, clock handling, and bus operations
|
||||
//! used across all Intel 400x family components (4001, 4002, 4003, 4004).
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
use crate::component::{BaseComponent, Component};
|
||||
use crate::pin::{Pin, PinValue};
|
||||
|
||||
// Memory operation state machine states (shared across all 400x chips)
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub 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
|
||||
}
|
||||
|
||||
|
||||
/// Common timing constants for Intel 400x series
|
||||
pub struct TimingConstants;
|
||||
|
||||
impl TimingConstants {
|
||||
pub const DEFAULT_ACCESS_TIME: Duration = Duration::from_nanos(500); // 500ns default
|
||||
pub const FAST_ACCESS_TIME: Duration = Duration::from_nanos(200); // 200ns for shift registers
|
||||
pub const ADDRESS_SETUP: Duration = Duration::from_nanos(100); // Address setup time
|
||||
pub const DATA_VALID: Duration = Duration::from_nanos(200); // Data valid delay
|
||||
}
|
||||
|
||||
/// Common clock edge detection and timing functionality
|
||||
pub trait Intel400xClockHandling {
|
||||
fn get_base(&self) -> &BaseComponent;
|
||||
|
||||
/// Read the two-phase clock pins from CPU
|
||||
/// Returns: (PHI1_value, PHI2_value)
|
||||
fn read_clock_pins(&self) -> (PinValue, PinValue) {
|
||||
let phi1 = self.get_clock_pin("PHI1");
|
||||
let phi2 = self.get_clock_pin("PHI2");
|
||||
(phi1, phi2)
|
||||
}
|
||||
|
||||
/// Get a clock pin value safely
|
||||
fn get_clock_pin(&self, name: &str) -> PinValue {
|
||||
if let Ok(pin) = self.get_base().get_pin(name) {
|
||||
if let Ok(pin_guard) = pin.lock() {
|
||||
pin_guard.read()
|
||||
} else {
|
||||
PinValue::Low
|
||||
}
|
||||
} else {
|
||||
PinValue::Low
|
||||
}
|
||||
}
|
||||
|
||||
/// Check for Φ1 rising edge
|
||||
fn is_phi1_rising_edge(&self, prev_phi1: PinValue) -> bool {
|
||||
let (phi1, _) = self.read_clock_pins();
|
||||
phi1 == PinValue::High && prev_phi1 == PinValue::Low
|
||||
}
|
||||
|
||||
/// Check for Φ1 falling edge
|
||||
fn is_phi1_falling_edge(&self, prev_phi1: PinValue) -> bool {
|
||||
let (phi1, _) = self.read_clock_pins();
|
||||
phi1 == PinValue::Low && prev_phi1 == PinValue::High
|
||||
}
|
||||
|
||||
/// Check for Φ2 rising edge
|
||||
fn is_phi2_rising_edge(&self, prev_phi2: PinValue) -> bool {
|
||||
let (_, phi2) = self.read_clock_pins();
|
||||
phi2 == PinValue::High && prev_phi2 == PinValue::Low
|
||||
}
|
||||
|
||||
/// Check for Φ2 falling edge
|
||||
fn is_phi2_falling_edge(&self, prev_phi2: PinValue) -> bool {
|
||||
let (_, phi2) = self.read_clock_pins();
|
||||
phi2 == PinValue::Low && prev_phi2 == PinValue::High
|
||||
}
|
||||
|
||||
/// Update clock state tracking for edge detection
|
||||
fn update_clock_states(&self, prev_phi1: &mut PinValue, prev_phi2: &mut PinValue) {
|
||||
let (phi1, phi2) = self.read_clock_pins();
|
||||
*prev_phi1 = phi1;
|
||||
*prev_phi2 = phi2;
|
||||
}
|
||||
}
|
||||
|
||||
/// Common data bus operations
|
||||
pub trait Intel400xDataBus {
|
||||
fn get_base(&self) -> &BaseComponent;
|
||||
|
||||
/// 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.get_base().get_pin(&format!("D{}", i)) {
|
||||
if let Ok(pin_guard) = pin.lock() {
|
||||
if pin_guard.read() == PinValue::High {
|
||||
data |= 1 << i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data & 0x0F
|
||||
}
|
||||
|
||||
/// 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;
|
||||
|
||||
for i in 0..4 {
|
||||
if let Ok(pin) = self.get_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
|
||||
};
|
||||
pin_guard.set_driver(Some(format!("{}_DATA", self.get_base().get_name())), pin_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Set data bus to high-impedance state to avoid bus contention
|
||||
fn tri_state_data_bus(&self) {
|
||||
for i in 0..4 {
|
||||
if let Ok(pin) = self.get_base().get_pin(&format!("D{}", i)) {
|
||||
if let Ok(mut pin_guard) = pin.lock() {
|
||||
pin_guard.set_driver(Some(format!("{}_DATA", self.get_base().get_name())), PinValue::HighZ);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Common address handling functionality
|
||||
pub trait Intel400xAddressHandling {
|
||||
fn get_base(&self) -> &BaseComponent;
|
||||
|
||||
/// 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(&self, high_nibble: Option<u8>, low_nibble: Option<u8>) -> Option<u16> {
|
||||
if let (Some(high), Some(low)) = (high_nibble, low_nibble) {
|
||||
Some(((high as u16) << 4) | (low as u16))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle address nibble latching during address phase
|
||||
/// Returns: (high_nibble, low_nibble, address_ready)
|
||||
fn handle_address_latching(
|
||||
&self,
|
||||
nibble: u8,
|
||||
address_high_nibble: &mut Option<u8>,
|
||||
address_low_nibble: &mut Option<u8>,
|
||||
full_address_ready: &mut bool,
|
||||
address_latch_time: &mut Option<Instant>,
|
||||
access_time: Duration,
|
||||
) {
|
||||
if address_high_nibble.is_none() {
|
||||
// First cycle: latch high nibble (bits 7-4)
|
||||
*address_high_nibble = Some(nibble);
|
||||
} else if address_low_nibble.is_none() {
|
||||
// Second cycle: latch low nibble (bits 3-0) and transition to latency wait
|
||||
*address_low_nibble = Some(nibble);
|
||||
|
||||
// Try to assemble full address
|
||||
if let Some(address) = self.assemble_full_address(*address_high_nibble, *address_low_nibble) {
|
||||
*full_address_ready = true;
|
||||
*address_latch_time = Some(Instant::now());
|
||||
|
||||
// Clear nibble storage for next address
|
||||
*address_high_nibble = None;
|
||||
*address_low_nibble = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle latency timing during wait state
|
||||
/// Returns: true if latency has elapsed
|
||||
fn handle_latency_wait(&self, address_latch_time: &Option<Instant>, access_time: Duration) -> bool {
|
||||
if let Some(latch_time) = address_latch_time {
|
||||
latch_time.elapsed() >= access_time
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Common control pin reading functionality
|
||||
pub trait Intel400xControlPins {
|
||||
fn get_base(&self) -> &BaseComponent;
|
||||
|
||||
/// Read SYNC pin state
|
||||
fn read_sync_pin(&self) -> bool {
|
||||
if let Ok(pin) = self.get_base().get_pin("SYNC") {
|
||||
if let Ok(pin_guard) = pin.lock() {
|
||||
pin_guard.read() == PinValue::High
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Read CM-ROM pin state
|
||||
fn read_cm_rom_pin(&self) -> bool {
|
||||
if let Ok(pin) = self.get_base().get_pin("CM") { // Note: CM pin name varies by component
|
||||
if let Ok(pin_guard) = pin.lock() {
|
||||
pin_guard.read() == PinValue::High
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Read RESET pin state
|
||||
fn read_reset_pin(&self) -> bool {
|
||||
if let Ok(pin) = self.get_base().get_pin("RESET") {
|
||||
if let Ok(pin_guard) = pin.lock() {
|
||||
pin_guard.read() == PinValue::High
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Common reset handling functionality
|
||||
pub trait Intel400xResetHandling {
|
||||
fn get_base(&self) -> &BaseComponent;
|
||||
|
||||
/// Handle system reset signal
|
||||
/// Hardware: RESET pin clears all internal state and tri-states outputs
|
||||
fn handle_reset(&self, reset_pin_name: &str) -> bool {
|
||||
let reset = if let Ok(pin) = self.get_base().get_pin(reset_pin_name) {
|
||||
if let Ok(pin_guard) = pin.lock() {
|
||||
pin_guard.read() == PinValue::High
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if reset {
|
||||
self.perform_reset();
|
||||
}
|
||||
|
||||
reset
|
||||
}
|
||||
|
||||
/// Perform the actual reset operations (to be implemented by each chip)
|
||||
fn perform_reset(&self);
|
||||
}
|
||||
|
||||
/// Common timing state machine functionality
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum TimingState {
|
||||
Idle, // No operation in progress
|
||||
AddressPhase, // Currently latching address nibbles
|
||||
WaitLatency, // Address latched, waiting for access time
|
||||
DriveData, // Latency elapsed, driving data on bus
|
||||
}
|
||||
|
||||
// Conversion implementations for compatibility with existing code
|
||||
impl From<MemoryState> for TimingState {
|
||||
fn from(state: MemoryState) -> Self {
|
||||
match state {
|
||||
MemoryState::Idle => TimingState::Idle,
|
||||
MemoryState::AddressPhase => TimingState::AddressPhase,
|
||||
MemoryState::WaitLatency => TimingState::WaitLatency,
|
||||
MemoryState::DriveData => TimingState::DriveData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TimingState> for MemoryState {
|
||||
fn from(state: TimingState) -> Self {
|
||||
match state {
|
||||
TimingState::Idle => MemoryState::Idle,
|
||||
TimingState::AddressPhase => MemoryState::AddressPhase,
|
||||
TimingState::WaitLatency => MemoryState::WaitLatency,
|
||||
TimingState::DriveData => MemoryState::DriveData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Forward declaration for RamState enum (defined in individual chip modules)
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum RamState {
|
||||
Idle,
|
||||
AddressPhase,
|
||||
WaitLatency,
|
||||
ReadData,
|
||||
WriteData,
|
||||
OutputPort,
|
||||
}
|
||||
|
||||
// Conversion implementations for compatibility with existing code
|
||||
impl From<RamState> for TimingState {
|
||||
fn from(state: RamState) -> Self {
|
||||
match state {
|
||||
RamState::Idle => TimingState::Idle,
|
||||
RamState::AddressPhase => TimingState::AddressPhase,
|
||||
RamState::WaitLatency => TimingState::WaitLatency,
|
||||
RamState::ReadData => TimingState::DriveData,
|
||||
RamState::WriteData => TimingState::DriveData,
|
||||
RamState::OutputPort => TimingState::DriveData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TimingState> for RamState {
|
||||
fn from(state: TimingState) -> Self {
|
||||
match state {
|
||||
TimingState::Idle => RamState::Idle,
|
||||
TimingState::AddressPhase => RamState::AddressPhase,
|
||||
TimingState::WaitLatency => RamState::WaitLatency,
|
||||
TimingState::DriveData => RamState::ReadData, // Default to ReadData for DriveData
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TimingState {
|
||||
pub fn is_idle(&self) -> bool {
|
||||
matches!(self, TimingState::Idle)
|
||||
}
|
||||
|
||||
pub fn is_address_phase(&self) -> bool {
|
||||
matches!(self, TimingState::AddressPhase)
|
||||
}
|
||||
|
||||
pub fn is_waiting_latency(&self) -> bool {
|
||||
matches!(self, TimingState::WaitLatency)
|
||||
}
|
||||
|
||||
pub fn is_driving_data(&self) -> bool {
|
||||
matches!(self, TimingState::DriveData)
|
||||
}
|
||||
}
|
||||
|
||||
/// Common timing state machine trait
|
||||
pub trait Intel400xTimingState {
|
||||
fn get_timing_state(&self) -> TimingState;
|
||||
fn set_timing_state(&mut self, state: TimingState);
|
||||
fn get_address_latch_time(&self) -> Option<Instant>;
|
||||
fn set_address_latch_time(&mut self, time: Option<Instant>);
|
||||
fn get_full_address_ready(&self) -> bool;
|
||||
fn set_full_address_ready(&mut self, ready: bool);
|
||||
fn get_address_high_nibble(&self) -> Option<u8>;
|
||||
fn set_address_high_nibble(&mut self, nibble: Option<u8>);
|
||||
fn get_address_low_nibble(&self) -> Option<u8>;
|
||||
fn set_address_low_nibble(&mut self, nibble: Option<u8>);
|
||||
fn get_access_time(&self) -> Duration;
|
||||
}
|
||||
|
||||
/// Utility functions for common operations
|
||||
pub mod utils {
|
||||
use super::*;
|
||||
|
||||
/// Create a unique driver name for a component
|
||||
pub fn create_driver_name(component_name: &str, suffix: &str) -> String {
|
||||
format!("{}_{}", component_name, suffix)
|
||||
}
|
||||
|
||||
/// Check if a pin value represents a logical high
|
||||
pub fn is_pin_high(pin: &Arc<Mutex<Pin>>) -> bool {
|
||||
if let Ok(pin_guard) = pin.lock() {
|
||||
pin_guard.read() == PinValue::High
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a pin value represents high impedance
|
||||
pub fn is_pin_high_z(pin: &Arc<Mutex<Pin>>) -> bool {
|
||||
if let Ok(pin_guard) = pin.lock() {
|
||||
pin_guard.read() == PinValue::HighZ
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Safely read a pin value with fallback
|
||||
pub fn read_pin_safe(pin: &Arc<Mutex<Pin>>, default: PinValue) -> PinValue {
|
||||
if let Ok(pin_guard) = pin.lock() {
|
||||
pin_guard.read()
|
||||
} else {
|
||||
default
|
||||
}
|
||||
}
|
||||
|
||||
/// Safely set a pin driver with error handling
|
||||
pub fn set_pin_driver_safe(pin: &Arc<Mutex<Pin>>, driver_name: String, value: PinValue) {
|
||||
if let Ok(mut pin_guard) = pin.lock() {
|
||||
pin_guard.set_driver(Some(driver_name), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Re-export commonly used items for convenience
|
||||
pub use utils::*;
|
||||
Loading…
Add table
Add a link
Reference in a new issue