Introduces a VirtualClock-based timing system replacing wall-clock Instant-based timing for hardware-accurate simulation. New modules: - virtual_clock.rs: Shared virtual time source with condvar sync - real_time_governor.rs: Thread that advances virtual time - component_timing.rs: Per-component timing constraints Key changes: - Component trait: added update_at_time(), timing(), set_virtual_clock_optional() - All Intel 400x components migrated to virtual-time-aware operation - ConfigurableSystem: start() returns Result, spawns governor thread - GUI: timing controls (speed slider, mode selector, step button) - Console: speed/mode/step/time commands - Backward compatible: legacy fallback for components without VirtualClock Reviewed-by: code review found 2 warnings (clock resume() dead code, reset() safety docs) — both fixed.
988 lines
36 KiB
Rust
988 lines
36 KiB
Rust
use parking_lot::Mutex;
|
|
use std::any::Any;
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
|
|
use crate::component::{BaseComponent, Component, RunnableComponent};
|
|
use crate::component_timing::ComponentTiming;
|
|
use crate::components::common::intel_400x::{
|
|
process_event_direct, Intel400xAddressHandling, Intel400xClockHandling,
|
|
Intel400xComponentBehavior, Intel400xComponentType, Intel400xControlPins, Intel400xDataBus,
|
|
Intel400xEvent, Intel400xResetHandling, Intel400xStateMachine, Intel400xTimingState,
|
|
Intel400xUnifiedState, TimingState,
|
|
};
|
|
use crate::pin::{Pin, PinValue};
|
|
use crate::virtual_clock::VirtualClock;
|
|
|
|
/// Intel 4003 - 10-bit serial-in, serial-out shift register
|
|
/// Part of the MCS-4 family, designed to work with Intel 4004 CPU
|
|
/// Features 10-bit shift register with serial input/output capability
|
|
///
|
|
/// Hardware Deviations:
|
|
/// - Timing simplified for testing (real hardware has specific clock requirements)
|
|
/// - I/O operations may have different timing than real hardware
|
|
pub struct Intel4003 {
|
|
pub base: BaseComponent,
|
|
shift_register: u16, // 10-bit shift register (bits 0-9 used)
|
|
input_buffer: u8, // 8-bit input buffer for serial data
|
|
output_buffer: u8, // 8-bit output buffer for serial data
|
|
access_time: Duration, // Shift register access latency (200ns)
|
|
serial_mode: SerialMode, // Current serial mode configuration
|
|
// Unified state machine implementation
|
|
state_machine: Intel400xStateMachine, // Unified state machine for Intel 400x components
|
|
// Clock edge detection
|
|
prev_phi1: PinValue, // Previous Φ1 clock state for edge detection
|
|
prev_phi2: PinValue, // Previous Φ2 clock state for edge detection
|
|
// Virtual time support (Phase 6)
|
|
virtual_clock: Option<Arc<VirtualClock>>, // Injected by ConfigurableSystem (Phase 7)
|
|
/// Virtual time snapshot for the current update cycle.
|
|
/// Set at the start of `update_at_time()` so all internal methods use a
|
|
/// consistent time value instead of reading `VirtualClock::now()` which
|
|
/// may have advanced since the cycle started.
|
|
current_update_time: Duration,
|
|
}
|
|
|
|
/// Serial operation modes for 4003 shift register
|
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
pub enum SerialMode {
|
|
Shift, // Shift mode (data shifts through register)
|
|
Load, // Load mode (data loaded into register)
|
|
Hold, // Hold mode (register holds current value)
|
|
}
|
|
|
|
impl Intel400xClockHandling for Intel4003 {
|
|
fn get_base(&self) -> &BaseComponent {
|
|
&self.base
|
|
}
|
|
}
|
|
|
|
impl Intel400xDataBus for Intel4003 {
|
|
fn get_base(&self) -> &BaseComponent {
|
|
&self.base
|
|
}
|
|
}
|
|
|
|
impl Intel400xAddressHandling for Intel4003 {
|
|
fn get_base(&self) -> &BaseComponent {
|
|
&self.base
|
|
}
|
|
}
|
|
|
|
impl Intel400xControlPins for Intel4003 {
|
|
fn get_base(&self) -> &BaseComponent {
|
|
&self.base
|
|
}
|
|
}
|
|
|
|
impl Intel400xResetHandling for Intel4003 {
|
|
fn get_base(&self) -> &BaseComponent {
|
|
&self.base
|
|
}
|
|
|
|
fn perform_reset(&mut self) {
|
|
// Reset operations specific to Intel4003
|
|
// Note: This is called from handle_reset, so we don't need to check reset pin again
|
|
self.state_machine.reset();
|
|
self.tri_state_data_bus();
|
|
|
|
// Clear shift register state
|
|
self.shift_register = 0;
|
|
self.input_buffer = 0;
|
|
self.output_buffer = 0;
|
|
self.serial_mode = SerialMode::Hold;
|
|
|
|
// Reset clock edge detection state to ensure proper edge detection after reset
|
|
// This is critical for tests that reset the chip between operations
|
|
self.prev_phi1 = PinValue::Low;
|
|
self.prev_phi2 = PinValue::Low;
|
|
}
|
|
}
|
|
|
|
impl Intel400xTimingState for Intel4003 {
|
|
fn get_timing_state(&self) -> TimingState {
|
|
// Convert from Intel400xUnifiedState to TimingState
|
|
match self.state_machine.get_state() {
|
|
Intel400xUnifiedState::Idle => TimingState::Idle,
|
|
Intel400xUnifiedState::AddressPhase => TimingState::AddressPhase,
|
|
Intel400xUnifiedState::WaitLatency => TimingState::WaitLatency,
|
|
Intel400xUnifiedState::DriveData => TimingState::DriveData,
|
|
Intel400xUnifiedState::ShiftData => TimingState::DriveData,
|
|
Intel400xUnifiedState::LoadData => TimingState::DriveData,
|
|
// Handle all other states
|
|
_ => TimingState::Idle,
|
|
}
|
|
}
|
|
|
|
fn set_timing_state(&mut self, state: TimingState) {
|
|
// Convert from TimingState to Intel400xUnifiedState
|
|
let unified_state = match state {
|
|
TimingState::Idle => Intel400xUnifiedState::Idle,
|
|
TimingState::AddressPhase => Intel400xUnifiedState::AddressPhase,
|
|
TimingState::WaitLatency => Intel400xUnifiedState::WaitLatency,
|
|
TimingState::DriveData => Intel400xUnifiedState::ShiftData, // Default to ShiftData
|
|
};
|
|
|
|
// Use state machine's reset method to transition to new state
|
|
// This is a compatibility method for legacy state handling
|
|
self.state_machine.reset();
|
|
self.state_machine.current_state = unified_state;
|
|
}
|
|
|
|
fn get_address_latch_time(&self) -> Option<Duration> {
|
|
self.state_machine.get_address_latch_time()
|
|
}
|
|
|
|
fn set_address_latch_time(&mut self, time: Option<Duration>) {
|
|
self.state_machine.set_address_latch_time(time);
|
|
}
|
|
|
|
fn get_full_address_ready(&self) -> bool {
|
|
self.state_machine.is_full_address_ready()
|
|
}
|
|
|
|
fn set_full_address_ready(&mut self, _ready: bool) {
|
|
// This is handled by state machine, but keep for compatibility
|
|
}
|
|
|
|
fn get_address_high_nibble(&self) -> Option<u8> {
|
|
self.state_machine.get_address_high_nibble()
|
|
}
|
|
|
|
fn set_address_high_nibble(&mut self, nibble: Option<u8>) {
|
|
self.state_machine.set_address_high_nibble(nibble);
|
|
}
|
|
|
|
fn get_address_low_nibble(&self) -> Option<u8> {
|
|
self.state_machine.get_address_low_nibble()
|
|
}
|
|
|
|
fn set_address_low_nibble(&mut self, nibble: Option<u8>) {
|
|
self.state_machine.set_address_low_nibble(nibble);
|
|
}
|
|
|
|
fn get_access_time(&self) -> Duration {
|
|
self.state_machine.get_access_time()
|
|
}
|
|
|
|
fn is_latency_elapsed_at(&self, virtual_time: Duration) -> bool {
|
|
self.state_machine.is_latency_elapsed_at(virtual_time)
|
|
}
|
|
}
|
|
|
|
impl Intel400xComponentBehavior for Intel4003 {
|
|
fn get_component_type(&self) -> Intel400xComponentType {
|
|
Intel400xComponentType::Intel4003
|
|
}
|
|
|
|
fn on_state_entry(&mut self, state: Intel400xUnifiedState) {
|
|
match state {
|
|
Intel400xUnifiedState::Idle => {
|
|
// Entry actions for Idle state
|
|
self.tri_state_data_bus();
|
|
}
|
|
Intel400xUnifiedState::AddressPhase => {
|
|
// Entry actions for AddressPhase state
|
|
// Address latching will happen in execute_state_logic
|
|
}
|
|
Intel400xUnifiedState::WaitLatency => {
|
|
// Entry actions for WaitLatency state
|
|
// Address is already latched, start access timer
|
|
}
|
|
Intel400xUnifiedState::ShiftData => {
|
|
// Entry actions for ShiftData state
|
|
// Data shifting will happen in execute_state_logic
|
|
}
|
|
Intel400xUnifiedState::LoadData => {
|
|
// Entry actions for LoadData state
|
|
// Data loading will happen in execute_state_logic
|
|
}
|
|
// Handle all other states
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
fn on_state_exit(&mut self, state: Intel400xUnifiedState) {
|
|
match state {
|
|
Intel400xUnifiedState::ShiftData | Intel400xUnifiedState::LoadData => {
|
|
// Exit actions for data states
|
|
self.tri_state_data_bus();
|
|
}
|
|
// Handle all other states
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
fn handle_component_event(&mut self, event: Intel400xEvent) -> bool {
|
|
match event {
|
|
Intel400xEvent::ClockPhi1Rising => {
|
|
// Handle Φ1 rising edge
|
|
self.handle_phi1_rising();
|
|
true // Event was handled
|
|
}
|
|
Intel400xEvent::ClockPhi1Falling => {
|
|
// Handle Φ1 falling edge
|
|
self.handle_phi1_falling();
|
|
true // Event was handled
|
|
}
|
|
Intel400xEvent::ClockPhi2Rising => {
|
|
// Handle Φ2 rising edge
|
|
self.handle_phi2_rising();
|
|
true // Event was handled
|
|
}
|
|
Intel400xEvent::ClockPhi2Falling => {
|
|
// Handle Φ2 falling edge
|
|
self.handle_phi2_falling();
|
|
true // Event was handled
|
|
}
|
|
Intel400xEvent::ResetActive => {
|
|
// Handle reset
|
|
self.perform_reset();
|
|
true // Event was handled
|
|
}
|
|
_ => false, // Let the generic state machine handle other events
|
|
}
|
|
}
|
|
|
|
fn execute_state_logic(&mut self, state: Intel400xUnifiedState) {
|
|
match state {
|
|
Intel400xUnifiedState::Idle => {
|
|
// In idle state, ensure bus is tri-stated
|
|
self.tri_state_data_bus();
|
|
}
|
|
Intel400xUnifiedState::AddressPhase => {
|
|
// Handle address latching during address phase
|
|
self.handle_address_latching();
|
|
}
|
|
Intel400xUnifiedState::WaitLatency => {
|
|
// Check if latency has elapsed using virtual time.
|
|
//
|
|
// **With VirtualClock (production):** `address_latch_time` was recorded
|
|
// by `handle_address_nibble_at()` in virtual time. We check if enough
|
|
// virtual time has elapsed since then (≥ 200ns access time).
|
|
//
|
|
// **Without VirtualClock (standalone tests):** `current_virtual_time()`
|
|
// returns `Duration::ZERO`, so `is_latency_elapsed_at()` would always
|
|
// return `false`. The `!has_clock` guard short-circuits to skip the wait.
|
|
//
|
|
// NOTE: The old `Instant::now()`-based code would naturally wait ~200ns.
|
|
// This new code skips the wait in legacy mode, making standalone tests
|
|
// less hardware-accurate but preventing them from hanging in WaitLatency.
|
|
let has_clock = self.virtual_clock.is_some();
|
|
let vtime = self.current_update_time;
|
|
if !has_clock || self.state_machine.is_latency_elapsed_at(vtime) {
|
|
self.process_latency_elapsed();
|
|
}
|
|
}
|
|
Intel400xUnifiedState::ShiftData => {
|
|
// Perform shift operation
|
|
self.handle_shift_operation();
|
|
}
|
|
Intel400xUnifiedState::LoadData => {
|
|
// Perform load operation
|
|
self.handle_load_operation();
|
|
}
|
|
// Handle other states (not used by Intel 4003)
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
fn responds_to_address(&self) -> bool {
|
|
// Intel 4003 mostly ignores address transmission
|
|
false
|
|
}
|
|
}
|
|
|
|
impl Intel4003 {
|
|
/// Create a new Intel 4003 shift register with specified access time
|
|
/// Parameters: name - Component identifier, access_time_ns - Access time in nanoseconds
|
|
/// Returns: New Intel4003 instance with configurable access timing
|
|
pub fn new(name: String) -> Self {
|
|
Self::new_with_access_time(name, 200) // Default 200ns access time (faster than ROM/RAM)
|
|
}
|
|
|
|
/// Create a new Intel 4003 shift register with custom access time (for testing)
|
|
/// Parameters: name - Component identifier, access_time_ns - Access time in nanoseconds
|
|
/// Returns: New Intel4003 instance with configurable access timing
|
|
pub fn new_with_access_time(name: String, access_time_ns: u64) -> Self {
|
|
// Intel 4003 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 shift register 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);
|
|
|
|
Intel4003 {
|
|
base: BaseComponent::new(name, pins),
|
|
shift_register: 0,
|
|
input_buffer: 0,
|
|
output_buffer: 0,
|
|
access_time: Duration::from_nanos(access_time_ns),
|
|
serial_mode: SerialMode::Hold,
|
|
state_machine: Intel400xStateMachine::new(
|
|
Intel400xComponentType::Intel4003,
|
|
Duration::from_nanos(access_time_ns),
|
|
),
|
|
prev_phi1: PinValue::Low,
|
|
prev_phi2: PinValue::Low,
|
|
virtual_clock: None,
|
|
current_update_time: Duration::ZERO,
|
|
}
|
|
}
|
|
|
|
/// Inject a VirtualClock for virtual time-based operation.
|
|
/// When set, `update()` uses virtual time and `run()` uses `wait_until()`.
|
|
/// This method is called by ConfigurableSystem in Phase 7.
|
|
pub fn set_virtual_clock(&mut self, clock: Arc<VirtualClock>) {
|
|
self.virtual_clock = Some(clock);
|
|
}
|
|
|
|
/// Get current virtual time, or Duration::ZERO if no VirtualClock is injected.
|
|
///
|
|
/// # Zero-Time Invariant
|
|
///
|
|
/// When no VirtualClock is injected, this returns `Duration::ZERO`. The `WaitLatency`
|
|
/// handler checks `vtime == Duration::ZERO` to skip latency enforcement in legacy mode.
|
|
/// This is intentional: legacy tests don't use VirtualClock and expect latency to be
|
|
/// skipped for backward compatibility.
|
|
fn current_virtual_time(&self) -> Duration {
|
|
self.virtual_clock.as_ref().map_or(Duration::ZERO, |vc| vc.now())
|
|
}
|
|
|
|
/// Set access time for simulation
|
|
/// Parameters: access_time_ns - Access time in nanoseconds
|
|
pub fn set_access_time(&mut self, access_time_ns: u64) {
|
|
self.access_time = Duration::from_nanos(access_time_ns);
|
|
self.state_machine
|
|
.set_access_time(Duration::from_nanos(access_time_ns));
|
|
}
|
|
|
|
/// Get current access time
|
|
/// Returns: Access time in nanoseconds
|
|
pub fn get_access_time(&self) -> u64 {
|
|
self.access_time.as_nanos() as u64
|
|
}
|
|
|
|
/// Tri-state data bus pins
|
|
fn tri_state_data_bus(&self) {
|
|
for i in 0..4 {
|
|
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
|
|
let mut pin_guard = pin.lock();
|
|
pin_guard.set_driver(Some(self.base.name()), PinValue::HighZ);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Handle Φ1 rising edge - Address and control phase
|
|
/// Hardware: Φ1 high = CPU drives bus with address/control information
|
|
/// Memory operations start when SYNC goes high during Φ1 rising edge
|
|
/// Focus: Address latching, reset, I/O operations
|
|
fn handle_phi1_rising(&mut self) {
|
|
// Handle system reset first (highest priority)
|
|
self.handle_reset("RESET");
|
|
|
|
// Check for shift register operation start on Φ1 rising edge with SYNC high
|
|
// Hardware: Shift register operations start when SYNC goes high during Φ1
|
|
// Note: SYNC marks instruction fetch start, but exact logic involves CPU cycle state.
|
|
// Shift register access: CM=1 (chip_select), CI=0 (!io_select)
|
|
// I/O access: CM=1 (chip_select), CI=1 (io_select)
|
|
let sync = self.read_sync_pin();
|
|
let chip_select = self.read_cm_rom_pin();
|
|
let io_select = self.read_ci_pin();
|
|
|
|
if sync && chip_select && !io_select {
|
|
// Start shift register operation on Φ1 rising edge (shift register access)
|
|
// Only start a new operation if we're currently idle
|
|
if self.state_machine.get_state() == Intel400xUnifiedState::Idle {
|
|
self.process_chip_select();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Handle Φ1 falling edge - End of address phase
|
|
/// Hardware: Φ1 low = End of CPU address driving phase
|
|
fn handle_phi1_falling(&mut self) {
|
|
// Intel 4003 ignores address transmission (responds_to_address() = false)
|
|
// No address latching needed for shift register operations
|
|
}
|
|
|
|
/// Handle Φ2 rising edge - Data phase
|
|
/// Hardware: Φ2 high = Peripherals drive bus with data
|
|
/// Focus: Data driving operations
|
|
fn handle_phi2_rising(&mut self) {
|
|
// Execute current state logic
|
|
self.execute_state_machine();
|
|
}
|
|
|
|
/// Handle Φ2 falling edge - End of data phase
|
|
/// Hardware: Φ2 low = End of data driving phase, time to tri-state bus
|
|
/// Data is held on bus during Φ2 high, tri-stated when Φ2 falls
|
|
/// Focus: Clean up and return to idle state
|
|
fn handle_phi2_falling(&mut self) {
|
|
match self.state_machine.get_state() {
|
|
Intel400xUnifiedState::ShiftData | Intel400xUnifiedState::LoadData => {
|
|
// End of data phase - tri-state bus and return to idle
|
|
self.tri_state_data_bus();
|
|
self.process_operation_complete();
|
|
}
|
|
|
|
Intel400xUnifiedState::Idle
|
|
| Intel400xUnifiedState::AddressPhase
|
|
| Intel400xUnifiedState::WaitLatency => {
|
|
// For other states, just ensure bus is tri-stated
|
|
self.tri_state_data_bus();
|
|
}
|
|
// Handle all other states
|
|
_ => {
|
|
// Default behavior for unhandled states
|
|
self.tri_state_data_bus();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Handle address nibble latching during address phase
|
|
/// Hardware: CPU provides 8-bit address as two 4-bit nibbles
|
|
/// Note: Intel 4003 mostly ignores address transmission for shift operations
|
|
fn handle_address_latching(&mut self) {
|
|
// Intel 4003 mostly ignores address transmission for shift operations
|
|
// But we still need to handle the address nibble for compatibility
|
|
let nibble = self.read_data_bus() & 0x0F; // Only 4 bits are valid for address nibbles
|
|
|
|
if self.state_machine.handle_address_nibble_at(nibble, self.current_update_time) {
|
|
// Address is complete — virtual time was recorded atomically by handle_address_nibble_at()
|
|
// Transition to latency wait
|
|
self.process_address_complete();
|
|
}
|
|
}
|
|
|
|
/// Handle shift register shift operation
|
|
/// Hardware: Shift register shifts data based on clock edges
|
|
fn handle_shift_operation(&mut self) {
|
|
// Read data from bus
|
|
let data = self.read_data_bus() & 0x0F; // Only 4 bits are valid
|
|
|
|
// Shift data into register
|
|
self.shift_register = ((self.shift_register << 4) | (data as u16)) & 0x03FF; // Keep only 10 bits
|
|
|
|
// Update output buffer with shifted-out bit
|
|
self.output_buffer = ((self.shift_register >> 9) & 1) as u8;
|
|
}
|
|
|
|
/// Handle shift register load operation
|
|
/// Hardware: Load data into shift register
|
|
fn handle_load_operation(&mut self) {
|
|
// Read data from bus
|
|
let data = self.read_data_bus() & 0x0F; // Only 4 bits are valid
|
|
|
|
// Load data into register
|
|
self.shift_register = (self.shift_register & 0xFF00) | (data as u16); // Keep lower 8 bits
|
|
|
|
// Update output buffer with loaded data
|
|
self.output_buffer = data;
|
|
}
|
|
|
|
/// Helper function to process latency elapsed event
|
|
fn process_latency_elapsed(&mut self) {
|
|
let old_state = self.state_machine.get_state();
|
|
let event = Intel400xEvent::LatencyElapsed;
|
|
let new_state = process_event_direct(&mut self.state_machine, event);
|
|
|
|
if new_state != old_state {
|
|
self.on_state_exit(old_state);
|
|
self.on_state_entry(new_state);
|
|
}
|
|
}
|
|
|
|
/// Helper function to process chip select event
|
|
fn process_chip_select(&mut self) {
|
|
let old_state = self.state_machine.get_state();
|
|
let event = Intel400xEvent::ChipSelect;
|
|
let new_state = process_event_direct(&mut self.state_machine, event);
|
|
|
|
if new_state != old_state {
|
|
self.on_state_exit(old_state);
|
|
self.on_state_entry(new_state);
|
|
}
|
|
}
|
|
|
|
/// Helper function to process operation complete event
|
|
fn process_operation_complete(&mut self) {
|
|
let old_state = self.state_machine.get_state();
|
|
let event = Intel400xEvent::OperationComplete;
|
|
let new_state = process_event_direct(&mut self.state_machine, event);
|
|
|
|
if new_state != old_state {
|
|
self.on_state_exit(old_state);
|
|
self.on_state_entry(new_state);
|
|
}
|
|
}
|
|
|
|
/// Helper function to process address complete event
|
|
fn process_address_complete(&mut self) {
|
|
let old_state = self.state_machine.get_state();
|
|
let event = Intel400xEvent::AddressComplete;
|
|
let new_state = process_event_direct(&mut self.state_machine, event);
|
|
|
|
if new_state != old_state {
|
|
self.on_state_exit(old_state);
|
|
self.on_state_entry(new_state);
|
|
}
|
|
}
|
|
|
|
/// Helper function to execute unified state machine logic
|
|
fn execute_state_machine(&mut self) {
|
|
// Execute the current state logic using the unified state machine
|
|
let current_state = self.state_machine.get_state();
|
|
self.execute_state_logic(current_state);
|
|
}
|
|
}
|
|
|
|
impl Component for Intel4003 {
|
|
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 - delegates to update_at_time with current virtual time
|
|
/// Hardware: Responds to Φ1 and Φ2 clock edges from CPU
|
|
fn update(&mut self) {
|
|
let vtime = self.current_virtual_time();
|
|
self.update_at_time(vtime);
|
|
}
|
|
|
|
/// Virtual-time-aware update cycle - handles clock edge detection and operation dispatch
|
|
/// Hardware: Responds to Φ1 and Φ2 clock edges from CPU
|
|
fn update_at_time(&mut self, virtual_time: Duration) {
|
|
// Snapshot virtual_time for the entire update cycle so internal methods
|
|
// use a consistent time instead of reading VirtualClock::now() which
|
|
// may have advanced.
|
|
self.current_update_time = virtual_time;
|
|
if !self.is_running() {
|
|
return;
|
|
}
|
|
|
|
// Check for reset first (highest priority, independent of clock edges)
|
|
// This matches Intel 4001 behavior where reset is processed immediately
|
|
let reset_active = self.read_reset_pin();
|
|
if reset_active {
|
|
self.perform_reset();
|
|
// Skip further processing if reset is active
|
|
return;
|
|
}
|
|
|
|
// Handle both rising and falling edges for proper two-phase operation
|
|
let phi1_rising = self.is_phi1_rising_edge(self.prev_phi1);
|
|
let phi1_falling = self.is_phi1_falling_edge(self.prev_phi1);
|
|
let phi2_rising = self.is_phi2_rising_edge(self.prev_phi2);
|
|
let phi2_falling = self.is_phi2_falling_edge(self.prev_phi2);
|
|
|
|
// Update clock states for next edge detection
|
|
let (phi1, phi2) = self.read_clock_pins();
|
|
self.prev_phi1 = phi1;
|
|
self.prev_phi2 = phi2;
|
|
|
|
// Collect all events to process
|
|
let mut events = Vec::new();
|
|
|
|
if phi1_rising {
|
|
events.push(Intel400xEvent::ClockPhi1Rising);
|
|
}
|
|
|
|
if phi1_falling {
|
|
events.push(Intel400xEvent::ClockPhi1Falling);
|
|
}
|
|
|
|
if phi2_rising {
|
|
events.push(Intel400xEvent::ClockPhi2Rising);
|
|
}
|
|
|
|
if phi2_falling {
|
|
events.push(Intel400xEvent::ClockPhi2Falling);
|
|
}
|
|
|
|
// Process all events
|
|
for event in events {
|
|
let old_state = self.state_machine.get_state();
|
|
let new_state = process_event_direct(&mut self.state_machine, event);
|
|
|
|
if new_state != old_state {
|
|
self.on_state_exit(old_state);
|
|
self.on_state_entry(new_state);
|
|
}
|
|
}
|
|
|
|
// Execute current state logic
|
|
self.execute_state_machine();
|
|
}
|
|
|
|
/// Run component with VirtualClock support or legacy fallback
|
|
/// 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;
|
|
|
|
// If no VirtualClock is injected, fall back to wall-clock run loop
|
|
let vclock = match self.virtual_clock.as_ref() {
|
|
Some(vc) => Arc::clone(vc),
|
|
None => {
|
|
// Legacy mode: use wall-clock timing
|
|
while self.is_running() {
|
|
self.update();
|
|
thread::sleep(Duration::from_micros(1));
|
|
}
|
|
return;
|
|
}
|
|
};
|
|
|
|
// Virtual time mode: wait for next tick to synchronize with clock
|
|
while self.is_running() {
|
|
let next_tick = vclock.now() + vclock.tick_size();
|
|
if let Some(current_vtime) = vclock.wait_until(next_tick) {
|
|
self.update_at_time(current_vtime);
|
|
} else {
|
|
// Clock was interrupted, exit
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Get timing constraints for Intel 4003 shift register
|
|
/// Returns: ComponentTiming with 200ns access time, 100ns propagation delay
|
|
fn timing(&self) -> ComponentTiming {
|
|
ComponentTiming::intel_4003()
|
|
}
|
|
|
|
/// 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();
|
|
}
|
|
|
|
fn is_running(&self) -> bool {
|
|
self.base.is_running()
|
|
}
|
|
|
|
fn set_running(&mut self, running: bool) {
|
|
self.base.set_running(running);
|
|
}
|
|
|
|
fn as_any(&self) -> &dyn Any {
|
|
self
|
|
}
|
|
|
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
|
self
|
|
}
|
|
|
|
fn set_virtual_clock_optional(&mut self, clock: Option<Arc<VirtualClock>>) {
|
|
self.virtual_clock = clock;
|
|
}
|
|
|
|
fn has_virtual_clock(&self) -> bool {
|
|
self.virtual_clock.is_some()
|
|
}
|
|
}
|
|
|
|
impl RunnableComponent for Intel4003 {}
|
|
|
|
// Intel 4003 specific methods
|
|
impl Intel4003 {
|
|
/// Get shift register value
|
|
/// Returns: Current 10-bit shift register value
|
|
pub fn get_shift_register(&self) -> u16 {
|
|
self.shift_register
|
|
}
|
|
|
|
/// Set shift register value (for testing/debugging)
|
|
/// Parameters: value - 10-bit value to set
|
|
pub fn set_shift_register(&mut self, value: u16) {
|
|
self.shift_register = value & 0x03FF; // Keep only 10 bits
|
|
}
|
|
|
|
/// Get input buffer value
|
|
/// Returns: Current 8-bit input buffer value
|
|
pub fn get_input_buffer(&self) -> u8 {
|
|
self.input_buffer
|
|
}
|
|
|
|
/// Set input buffer value (for testing/debugging)
|
|
/// Parameters: value - 8-bit value to set
|
|
pub fn set_input_buffer(&mut self, value: u8) {
|
|
self.input_buffer = value;
|
|
}
|
|
|
|
/// Get output buffer value
|
|
/// Returns: Current 8-bit output buffer value
|
|
pub fn get_output_buffer(&self) -> u8 {
|
|
self.output_buffer
|
|
}
|
|
|
|
/// Set output buffer value (for testing/debugging)
|
|
/// Parameters: value - 8-bit value to set
|
|
pub fn set_output_buffer(&mut self, value: u8) {
|
|
self.output_buffer = value;
|
|
}
|
|
|
|
/// Get serial mode
|
|
/// Returns: Current serial mode
|
|
pub fn get_serial_mode(&self) -> SerialMode {
|
|
self.serial_mode
|
|
}
|
|
|
|
/// Set serial mode (for testing/debugging)
|
|
/// Parameters: mode - Serial mode to set
|
|
pub fn set_serial_mode(&mut self, mode: SerialMode) {
|
|
self.serial_mode = mode;
|
|
}
|
|
|
|
/// 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: {:?}, Register: 0x{:03X}, Input: 0x{:X}, Output: 0x{:X}",
|
|
test_name,
|
|
self.state_machine.get_state(),
|
|
self.shift_register,
|
|
self.input_buffer,
|
|
self.output_buffer
|
|
);
|
|
}
|
|
}
|
|
|
|
// Custom formatter for debugging
|
|
impl std::fmt::Display for SerialMode {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self {
|
|
SerialMode::Shift => write!(f, "Shift"),
|
|
SerialMode::Load => write!(f, "Load"),
|
|
SerialMode::Hold => write!(f, "Hold"),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_intel4003_creation() {
|
|
let shift_reg = Intel4003::new("SHIFT_4003".to_string());
|
|
assert_eq!(shift_reg.name(), "SHIFT_4003");
|
|
assert_eq!(shift_reg.get_access_time(), 200); // Default 200ns
|
|
assert!(!shift_reg.is_running());
|
|
}
|
|
|
|
#[test]
|
|
fn test_intel4003_shift_operations() {
|
|
let mut shift_reg = Intel4003::new("SHIFT_4003".to_string());
|
|
|
|
// Test shift register operations
|
|
shift_reg.set_shift_register(0x000);
|
|
assert_eq!(shift_reg.get_shift_register(), 0x000);
|
|
|
|
// Test shifting in data - handle_shift_operation reads from data bus
|
|
// Since data bus is in HighZ state, it will read 0
|
|
shift_reg.set_shift_register(0x001);
|
|
shift_reg.handle_shift_operation();
|
|
assert_eq!(shift_reg.get_shift_register(), 0x010); // 0x001 << 4 | 0 = 0x010
|
|
|
|
// Test multiple shifts - all will shift in 0 due to HighZ data bus
|
|
shift_reg.handle_shift_operation(); // Shift in 0x0
|
|
assert_eq!(shift_reg.get_shift_register(), 0x100); // 0x010 << 4 | 0 = 0x100
|
|
shift_reg.handle_shift_operation(); // Shift in 0x0
|
|
assert_eq!(shift_reg.get_shift_register(), 0x000); // 0x100 << 4 | 0 = 0x1000, masked to 10 bits = 0x000
|
|
shift_reg.handle_shift_operation(); // Shift in 0x0
|
|
assert_eq!(shift_reg.get_shift_register(), 0x000); // 0x000 << 4 | 0 = 0x000
|
|
}
|
|
|
|
#[test]
|
|
fn test_intel4003_load_operations() {
|
|
let mut shift_reg = Intel4003::new("SHIFT_4003".to_string());
|
|
|
|
// Test load operations
|
|
shift_reg.set_shift_register(0x000);
|
|
assert_eq!(shift_reg.get_shift_register(), 0x000);
|
|
|
|
// Load data - handle_load_operation reads from data bus
|
|
// Since data bus is in HighZ state, it will read 0
|
|
shift_reg.handle_load_operation();
|
|
assert_eq!(shift_reg.get_shift_register(), 0x000); // (0x000 & 0xFF00) | 0 = 0x000
|
|
assert_eq!(shift_reg.get_output_buffer(), 0x00);
|
|
|
|
// Load data again - still 0 due to HighZ data bus
|
|
shift_reg.handle_load_operation();
|
|
assert_eq!(shift_reg.get_shift_register(), 0x000); // (0x000 & 0xFF00) | 0 = 0x000
|
|
assert_eq!(shift_reg.get_output_buffer(), 0x00);
|
|
}
|
|
|
|
#[test]
|
|
fn test_intel4003_serial_modes() {
|
|
let mut shift_reg = Intel4003::new("MODE_4003".to_string());
|
|
|
|
// Test initial mode
|
|
assert_eq!(shift_reg.get_serial_mode(), SerialMode::Hold);
|
|
|
|
// Test setting modes
|
|
shift_reg.set_serial_mode(SerialMode::Shift);
|
|
assert_eq!(shift_reg.get_serial_mode(), SerialMode::Shift);
|
|
|
|
shift_reg.set_serial_mode(SerialMode::Load);
|
|
assert_eq!(shift_reg.get_serial_mode(), SerialMode::Load);
|
|
|
|
shift_reg.set_serial_mode(SerialMode::Hold);
|
|
assert_eq!(shift_reg.get_serial_mode(), SerialMode::Hold);
|
|
}
|
|
|
|
#[test]
|
|
fn test_intel4003_buffers() {
|
|
let mut shift_reg = Intel4003::new("BUFFER_4003".to_string());
|
|
|
|
// Test buffer operations
|
|
assert_eq!(shift_reg.get_input_buffer(), 0);
|
|
assert_eq!(shift_reg.get_output_buffer(), 0);
|
|
|
|
shift_reg.set_input_buffer(0x12);
|
|
assert_eq!(shift_reg.get_input_buffer(), 0x12);
|
|
|
|
shift_reg.set_output_buffer(0x34);
|
|
assert_eq!(shift_reg.get_output_buffer(), 0x34);
|
|
}
|
|
|
|
#[test]
|
|
fn test_intel4003_configurable_access_time() {
|
|
let mut shift_reg = Intel4003::new("TIME_4003".to_string());
|
|
|
|
// Test default access time
|
|
assert_eq!(shift_reg.get_access_time(), 200);
|
|
|
|
// Test setting custom access time
|
|
shift_reg.set_access_time(100);
|
|
assert_eq!(shift_reg.get_access_time(), 100);
|
|
|
|
// Test constructor with custom access time
|
|
let fast_shift = Intel4003::new_with_access_time("FAST_SHIFT".to_string(), 1);
|
|
assert_eq!(fast_shift.get_access_time(), 1);
|
|
assert_eq!(fast_shift.name(), "FAST_SHIFT");
|
|
}
|
|
|
|
#[test]
|
|
fn test_intel4003_state_transitions() {
|
|
let mut shift_reg = Intel4003::new_with_access_time("StateTestSHIFT".to_string(), 1);
|
|
|
|
// Test all state transitions
|
|
let states = vec![
|
|
TimingState::Idle,
|
|
TimingState::AddressPhase,
|
|
TimingState::WaitLatency,
|
|
TimingState::DriveData,
|
|
];
|
|
|
|
for &state in &states {
|
|
shift_reg.set_timing_state(state);
|
|
assert_eq!(shift_reg.get_timing_state(), state);
|
|
}
|
|
|
|
// Test return to idle
|
|
shift_reg.set_timing_state(TimingState::Idle);
|
|
assert_eq!(shift_reg.get_timing_state(), TimingState::Idle);
|
|
}
|
|
|
|
#[test]
|
|
fn test_intel4003_address_handling() {
|
|
let mut shift_reg = Intel4003::new_with_access_time("AddressTestSHIFT".to_string(), 1);
|
|
|
|
// Test address nibble handling
|
|
shift_reg.set_address_high_nibble(Some(0x12));
|
|
shift_reg.set_address_low_nibble(Some(0x34));
|
|
shift_reg.set_full_address_ready(true);
|
|
|
|
assert_eq!(shift_reg.get_address_high_nibble(), Some(0x12));
|
|
assert_eq!(shift_reg.get_address_low_nibble(), Some(0x34));
|
|
assert_eq!(shift_reg.state_machine.is_full_address_ready(), true);
|
|
|
|
// Test clearing
|
|
shift_reg.set_address_high_nibble(None);
|
|
shift_reg.set_address_low_nibble(None);
|
|
shift_reg.set_full_address_ready(false);
|
|
|
|
assert_eq!(shift_reg.get_address_high_nibble(), None);
|
|
assert_eq!(shift_reg.get_address_low_nibble(), None);
|
|
assert_eq!(shift_reg.state_machine.is_full_address_ready(), false);
|
|
}
|
|
|
|
#[test]
|
|
fn test_intel4003_reset_behavior() {
|
|
let mut shift_reg = Intel4003::new("ResetTestSHIFT".to_string());
|
|
|
|
// Set up some state first
|
|
shift_reg.set_shift_register(0x123);
|
|
shift_reg.set_input_buffer(0x45);
|
|
shift_reg.set_output_buffer(0x67);
|
|
shift_reg.set_serial_mode(SerialMode::Shift);
|
|
|
|
// Verify state is set
|
|
assert_eq!(shift_reg.get_shift_register(), 0x123);
|
|
assert_eq!(shift_reg.get_input_buffer(), 0x45);
|
|
assert_eq!(shift_reg.get_output_buffer(), 0x67);
|
|
assert_eq!(shift_reg.get_serial_mode(), SerialMode::Shift);
|
|
|
|
// Perform reset
|
|
shift_reg.perform_reset();
|
|
|
|
// Verify all state is cleared
|
|
assert_eq!(shift_reg.get_shift_register(), 0);
|
|
assert_eq!(shift_reg.get_input_buffer(), 0);
|
|
assert_eq!(shift_reg.get_output_buffer(), 0);
|
|
assert_eq!(shift_reg.get_serial_mode(), SerialMode::Hold);
|
|
}
|
|
|
|
#[test]
|
|
fn test_intel4003_debug_function() {
|
|
let mut shift_reg = Intel4003::new("DebugTestSHIFT".to_string());
|
|
|
|
// Test debug state transitions
|
|
shift_reg.set_shift_register(0x123);
|
|
shift_reg.set_input_buffer(0x45);
|
|
shift_reg.set_output_buffer(0x67);
|
|
|
|
// Call debug function
|
|
shift_reg.debug_state_transitions("TestDebug");
|
|
|
|
// Should not panic or crash
|
|
assert_eq!(shift_reg.get_shift_register(), 0x123);
|
|
assert_eq!(shift_reg.get_input_buffer(), 0x45);
|
|
assert_eq!(shift_reg.get_output_buffer(), 0x67);
|
|
}
|
|
}
|