rusty_emu/src/components/memory/intel_4003.rs
Jan-Peter von Hunnius 08f53d68fe feat: implement virtual clock timing architecture (Phases 5-8)
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.
2026-05-14 21:00:22 +02:00

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);
}
}