Extracted common 400x behavior like timing and latching into common module Added maxed MCS-4
392 lines
No EOL
16 KiB
Rust
392 lines
No EOL
16 KiB
Rust
//! Mock-based tests for Intel 400x data bus and pin operations
|
|
//!
|
|
//! These tests use the mock implementations to verify the behavior of
|
|
//! the intel_400x traits when interacting with hardware components.
|
|
|
|
use rusty_emu::components::common::intel_400x::*;
|
|
use rusty_emu::pin::PinValue;
|
|
use crate::mocks::*;
|
|
use std::time::Duration;
|
|
|
|
#[cfg(test)]
|
|
mod data_bus_tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_data_bus_read_write_operations() {
|
|
let mut scenario = MockScenario::new("TestDataBus");
|
|
|
|
// Test writing data to bus
|
|
scenario.set_data_bus_value(0x05); // 0101 in binary
|
|
assert_eq!(scenario.get_data_bus_value(), 0x05);
|
|
|
|
// Verify individual bits
|
|
assert_eq!(scenario.component.get_pin_value("D0"), Some(PinValue::High)); // LSB
|
|
assert_eq!(scenario.component.get_pin_value("D1"), Some(PinValue::Low));
|
|
assert_eq!(scenario.component.get_pin_value("D2"), Some(PinValue::High));
|
|
assert_eq!(scenario.component.get_pin_value("D3"), Some(PinValue::Low)); // MSB
|
|
|
|
// Test writing different value
|
|
scenario.set_data_bus_value(0x0A); // 1010 in binary
|
|
assert_eq!(scenario.get_data_bus_value(), 0x0A);
|
|
|
|
assert_eq!(scenario.component.get_pin_value("D0"), Some(PinValue::Low));
|
|
assert_eq!(scenario.component.get_pin_value("D1"), Some(PinValue::High));
|
|
assert_eq!(scenario.component.get_pin_value("D2"), Some(PinValue::Low));
|
|
assert_eq!(scenario.component.get_pin_value("D3"), Some(PinValue::High));
|
|
}
|
|
|
|
#[test]
|
|
fn test_data_bus_all_values() {
|
|
let mut scenario = MockScenario::new("TestAllDataValues");
|
|
|
|
// Test all possible 4-bit values
|
|
for value in 0u8..16 {
|
|
scenario.set_data_bus_value(value);
|
|
let read_value = scenario.get_data_bus_value();
|
|
assert_eq!(value, read_value, "Failed for value 0x{:02X}", value);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_pin_operation_counting() {
|
|
let scenario = MockScenario::new("TestPinCounting");
|
|
|
|
// Set some pin values and check that operations are counted
|
|
scenario.component.set_pin_value("D0", PinValue::High);
|
|
scenario.component.set_pin_value("D1", PinValue::Low);
|
|
|
|
// Note: Our current mock doesn't fully implement the counting
|
|
// This test demonstrates the structure for when we enhance the mock
|
|
assert_eq!(scenario.component.get_pin_value("D0"), Some(PinValue::High));
|
|
assert_eq!(scenario.component.get_pin_value("D1"), Some(PinValue::Low));
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod clock_handling_tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_clock_pin_reading() {
|
|
let scenario = MockScenario::new("TestClockReading");
|
|
|
|
// Test reading clock pins in different states
|
|
scenario.component.set_clock_values(PinValue::Low, PinValue::Low);
|
|
// In a real implementation, this would test the actual trait methods
|
|
// For now, we verify the mock setup works correctly
|
|
|
|
assert_eq!(scenario.component.get_pin_value("PHI1"), Some(PinValue::Low));
|
|
assert_eq!(scenario.component.get_pin_value("PHI2"), Some(PinValue::Low));
|
|
}
|
|
|
|
#[test]
|
|
fn test_clock_edge_scenarios() {
|
|
let mut scenario = MockScenario::new("TestClockEdges");
|
|
|
|
// Test PHI1 rising edge
|
|
scenario.set_phi1_rising_edge();
|
|
assert_eq!(scenario.component.get_pin_value("PHI1"), Some(PinValue::High));
|
|
assert_eq!(scenario.component.get_pin_value("PHI2"), Some(PinValue::Low));
|
|
|
|
// Test PHI1 falling edge
|
|
scenario.set_phi1_falling_edge();
|
|
assert_eq!(scenario.component.get_pin_value("PHI1"), Some(PinValue::Low));
|
|
assert_eq!(scenario.component.get_pin_value("PHI2"), Some(PinValue::Low));
|
|
|
|
// Test PHI2 rising edge
|
|
scenario.set_phi2_rising_edge();
|
|
assert_eq!(scenario.component.get_pin_value("PHI1"), Some(PinValue::Low));
|
|
assert_eq!(scenario.component.get_pin_value("PHI2"), Some(PinValue::High));
|
|
|
|
// Test PHI2 falling edge
|
|
scenario.set_phi2_falling_edge();
|
|
assert_eq!(scenario.component.get_pin_value("PHI1"), Some(PinValue::Low));
|
|
assert_eq!(scenario.component.get_pin_value("PHI2"), Some(PinValue::Low));
|
|
}
|
|
|
|
#[test]
|
|
fn test_clock_state_transitions() {
|
|
let mut scenario = MockScenario::new("TestClockTransitions");
|
|
|
|
// Simulate a clock cycle: Low -> High -> Low
|
|
scenario.set_clock_low();
|
|
assert_eq!(scenario.component.get_pin_value("PHI1"), Some(PinValue::Low));
|
|
assert_eq!(scenario.component.get_pin_value("PHI2"), Some(PinValue::Low));
|
|
|
|
scenario.set_clock_high();
|
|
assert_eq!(scenario.component.get_pin_value("PHI1"), Some(PinValue::High));
|
|
assert_eq!(scenario.component.get_pin_value("PHI2"), Some(PinValue::High));
|
|
|
|
scenario.set_clock_low();
|
|
assert_eq!(scenario.component.get_pin_value("PHI1"), Some(PinValue::Low));
|
|
assert_eq!(scenario.component.get_pin_value("PHI2"), Some(PinValue::Low));
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod control_pin_tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_control_pin_reading() {
|
|
let scenario = MockScenario::new("TestControlPins");
|
|
|
|
// Test SYNC pin
|
|
scenario.set_pin_value("SYNC", PinValue::High);
|
|
assert_eq!(scenario.component.get_pin_value("SYNC"), Some(PinValue::High));
|
|
|
|
scenario.set_pin_value("SYNC", PinValue::Low);
|
|
assert_eq!(scenario.component.get_pin_value("SYNC"), Some(PinValue::Low));
|
|
|
|
// Test CM-ROM pin
|
|
scenario.component.set_pin_value("CM", PinValue::High);
|
|
assert_eq!(scenario.component.get_pin_value("CM"), Some(PinValue::High));
|
|
|
|
scenario.component.set_pin_value("CM", PinValue::Low);
|
|
assert_eq!(scenario.component.get_pin_value("CM"), Some(PinValue::Low));
|
|
|
|
// Test RESET pin
|
|
scenario.component.set_pin_value("RESET", PinValue::High);
|
|
assert_eq!(scenario.component.get_pin_value("RESET"), Some(PinValue::High));
|
|
|
|
scenario.component.set_pin_value("RESET", PinValue::Low);
|
|
assert_eq!(scenario.component.get_pin_value("RESET"), Some(PinValue::Low));
|
|
}
|
|
|
|
#[test]
|
|
fn test_reset_functionality() {
|
|
let mut scenario = MockScenario::new("TestReset");
|
|
|
|
// Set up some state
|
|
scenario.component.set_timing_state(TimingState::DriveData);
|
|
scenario.component.set_full_address_ready(true);
|
|
scenario.component.set_address_high_nibble(Some(0x12));
|
|
scenario.component.set_address_low_nibble(Some(0x34));
|
|
scenario.set_data_bus_value(0x0F);
|
|
|
|
// Verify state is set
|
|
assert_eq!(scenario.component.get_timing_state(), TimingState::DriveData);
|
|
assert_eq!(scenario.component.get_full_address_ready(), true);
|
|
assert_eq!(scenario.component.get_address_high_nibble(), Some(0x12));
|
|
assert_eq!(scenario.component.get_address_low_nibble(), Some(0x34));
|
|
assert_eq!(scenario.get_data_bus_value(), 0x0F);
|
|
|
|
// Perform reset
|
|
scenario.component.perform_reset();
|
|
|
|
// Verify reset behavior
|
|
assert_eq!(scenario.component.get_timing_state(), TimingState::Idle);
|
|
assert_eq!(scenario.component.get_full_address_ready(), false);
|
|
assert_eq!(scenario.component.get_address_high_nibble(), None);
|
|
assert_eq!(scenario.component.get_address_low_nibble(), None);
|
|
|
|
// Data bus should be tri-stated (HighZ)
|
|
for i in 0..4 {
|
|
let pin_name = format!("D{}", i);
|
|
assert_eq!(scenario.component.get_pin_value(&pin_name), Some(PinValue::HighZ));
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod timing_state_tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_timing_state_machine_transitions() {
|
|
let mut scenario = MockScenario::new("TestTimingStateMachine");
|
|
|
|
// Start in idle state
|
|
assert_eq!(scenario.component.get_timing_state(), TimingState::Idle);
|
|
assert!(scenario.component.get_timing_state().is_idle());
|
|
assert!(!scenario.component.get_timing_state().is_address_phase());
|
|
|
|
// Transition to address phase
|
|
scenario.component.set_timing_state(TimingState::AddressPhase);
|
|
assert_eq!(scenario.component.get_timing_state(), TimingState::AddressPhase);
|
|
assert!(!scenario.component.get_timing_state().is_idle());
|
|
assert!(scenario.component.get_timing_state().is_address_phase());
|
|
|
|
// Transition to wait latency
|
|
scenario.component.set_timing_state(TimingState::WaitLatency);
|
|
assert_eq!(scenario.component.get_timing_state(), TimingState::WaitLatency);
|
|
assert!(scenario.component.get_timing_state().is_waiting_latency());
|
|
|
|
// Transition to drive data
|
|
scenario.component.set_timing_state(TimingState::DriveData);
|
|
assert_eq!(scenario.component.get_timing_state(), TimingState::DriveData);
|
|
assert!(scenario.component.get_timing_state().is_driving_data());
|
|
}
|
|
|
|
#[test]
|
|
fn test_address_latch_timing() {
|
|
let mut scenario = MockScenario::new("TestAddressLatchTiming");
|
|
|
|
// Set up address latching
|
|
scenario.component.set_address_high_nibble(Some(0x12));
|
|
scenario.component.set_address_low_nibble(Some(0x34));
|
|
scenario.component.set_full_address_ready(true);
|
|
|
|
// Record latch time
|
|
let latch_time = scenario.time_provider.now();
|
|
scenario.component.set_address_latch_time(Some(latch_time));
|
|
|
|
// Verify state
|
|
assert_eq!(scenario.component.get_address_high_nibble(), Some(0x12));
|
|
assert_eq!(scenario.component.get_address_low_nibble(), Some(0x34));
|
|
assert_eq!(scenario.component.get_full_address_ready(), true);
|
|
assert_eq!(scenario.component.get_address_latch_time(), Some(latch_time));
|
|
|
|
// Test clearing the latch
|
|
scenario.component.set_address_high_nibble(None);
|
|
scenario.component.set_address_low_nibble(None);
|
|
scenario.component.set_full_address_ready(false);
|
|
scenario.component.set_address_latch_time(None);
|
|
|
|
assert_eq!(scenario.component.get_address_high_nibble(), None);
|
|
assert_eq!(scenario.component.get_address_low_nibble(), None);
|
|
assert_eq!(scenario.component.get_full_address_ready(), false);
|
|
assert_eq!(scenario.component.get_address_latch_time(), None);
|
|
}
|
|
|
|
#[test]
|
|
fn test_access_time_configuration() {
|
|
let scenario = MockScenario::new("TestAccessTime");
|
|
|
|
// Test default access time
|
|
assert_eq!(scenario.component.get_access_time(), TimingConstants::DEFAULT_ACCESS_TIME);
|
|
|
|
// In a real implementation, we would test changing the access time
|
|
// For now, we verify the getter works
|
|
let access_time = scenario.component.get_access_time();
|
|
assert!(access_time > Duration::from_nanos(0));
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod integration_scenarios {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_complete_memory_operation_cycle() {
|
|
let mut scenario = MockScenario::new("TestMemoryCycle");
|
|
|
|
// Initial state
|
|
assert_eq!(scenario.component.get_timing_state(), TimingState::Idle);
|
|
|
|
// Phase 1: Address phase - high nibble
|
|
scenario.component.set_timing_state(TimingState::AddressPhase);
|
|
scenario.component.set_address_high_nibble(Some(0x12));
|
|
assert_eq!(scenario.component.get_timing_state(), TimingState::AddressPhase);
|
|
assert_eq!(scenario.component.get_address_high_nibble(), Some(0x12));
|
|
|
|
// Phase 2: Address phase - low nibble
|
|
scenario.component.set_address_low_nibble(Some(0x34));
|
|
scenario.component.set_full_address_ready(true);
|
|
let latch_time = scenario.time_provider.now();
|
|
scenario.component.set_address_latch_time(Some(latch_time));
|
|
|
|
// Verify address assembly
|
|
assert_eq!(scenario.component.get_address_high_nibble(), Some(0x12));
|
|
assert_eq!(scenario.component.get_address_low_nibble(), Some(0x34));
|
|
assert_eq!(scenario.component.get_full_address_ready(), true);
|
|
|
|
// Phase 3: Wait for latency
|
|
scenario.component.set_timing_state(TimingState::WaitLatency);
|
|
assert_eq!(scenario.component.get_timing_state(), TimingState::WaitLatency);
|
|
|
|
// Advance time past access time
|
|
scenario.advance_time(scenario.component.get_access_time() + Duration::from_nanos(10));
|
|
|
|
// Phase 4: Drive data
|
|
scenario.component.set_timing_state(TimingState::DriveData);
|
|
scenario.set_data_bus_value(0x0F); // Drive some data
|
|
assert_eq!(scenario.component.get_timing_state(), TimingState::DriveData);
|
|
assert_eq!(scenario.get_data_bus_value(), 0x0F);
|
|
|
|
// Cycle complete - return to idle
|
|
scenario.component.set_timing_state(TimingState::Idle);
|
|
assert_eq!(scenario.component.get_timing_state(), TimingState::Idle);
|
|
}
|
|
|
|
#[test]
|
|
fn test_bus_contention_avoidance() {
|
|
let mut scenario = MockScenario::new("TestBusContention");
|
|
|
|
// Set up a scenario where bus contention could occur
|
|
scenario.set_data_bus_value(0x0F);
|
|
|
|
// In a real implementation, we would test tri-stating the bus
|
|
// to avoid contention with other devices
|
|
for i in 0..4 {
|
|
let pin_name = format!("D{}", i);
|
|
scenario.component.set_pin_value(&pin_name, PinValue::HighZ);
|
|
assert_eq!(scenario.component.get_pin_value(&pin_name), Some(PinValue::HighZ));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_multiple_address_cycles() {
|
|
let mut scenario = MockScenario::new("TestMultipleAddresses");
|
|
|
|
// First address cycle
|
|
scenario.component.set_address_high_nibble(Some(0x12));
|
|
scenario.component.set_address_low_nibble(Some(0x34));
|
|
scenario.component.set_full_address_ready(true);
|
|
|
|
assert_eq!(scenario.component.get_address_high_nibble(), Some(0x12));
|
|
assert_eq!(scenario.component.get_address_low_nibble(), Some(0x34));
|
|
|
|
// Clear for next cycle
|
|
scenario.component.set_address_high_nibble(None);
|
|
scenario.component.set_address_low_nibble(None);
|
|
scenario.component.set_full_address_ready(false);
|
|
|
|
// Second address cycle
|
|
scenario.component.set_address_high_nibble(Some(0x56));
|
|
scenario.component.set_address_low_nibble(Some(0x78));
|
|
scenario.component.set_full_address_ready(true);
|
|
|
|
assert_eq!(scenario.component.get_address_high_nibble(), Some(0x56));
|
|
assert_eq!(scenario.component.get_address_low_nibble(), Some(0x78));
|
|
assert_eq!(scenario.component.get_full_address_ready(), true);
|
|
|
|
// First address should be cleared
|
|
assert_eq!(scenario.component.get_address_high_nibble(), Some(0x56));
|
|
assert_eq!(scenario.component.get_address_low_nibble(), Some(0x78));
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod error_handling_tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_missing_pin_handling() {
|
|
let scenario = MockScenario::new("TestMissingPins");
|
|
|
|
// Test accessing non-existent pins
|
|
assert_eq!(scenario.component.get_pin_value("NONEXISTENT"), None);
|
|
|
|
// Standard pins should exist
|
|
assert_eq!(scenario.component.get_pin_value("D0"), Some(PinValue::HighZ));
|
|
assert_eq!(scenario.component.get_pin_value("PHI1"), Some(PinValue::Low));
|
|
assert_eq!(scenario.component.get_pin_value("SYNC"), Some(PinValue::Low));
|
|
}
|
|
|
|
#[test]
|
|
fn test_invalid_pin_operations() {
|
|
let scenario = MockScenario::new("TestInvalidOperations");
|
|
|
|
// Test setting pin to HighZ (tri-state)
|
|
scenario.component.set_pin_value("D0", PinValue::HighZ);
|
|
assert_eq!(scenario.component.get_pin_value("D0"), Some(PinValue::HighZ));
|
|
|
|
// Test that HighZ is handled correctly in bus operations
|
|
// (This would be more relevant in actual hardware interface tests)
|
|
let bus_value = scenario.get_data_bus_value();
|
|
// The exact behavior depends on how HighZ pins are interpreted
|
|
// This test documents the expected structure
|
|
}
|
|
} |