Overhaul the Intel 4004 instruction set to match the real opcode map, adding missing instructions (NOP, FIM, FIN, JIN, ISZ, KBP, DCL, WR0-3, RD0-3) and refining two-byte instruction handling with proper M1/M2 data phases. Rewrite the CPU fetch-execute cycle to correctly process two-byte opcodes across separate clock cycles. Improve Intel 4001 ROM timing with M1/M2 nibble phase tracking and an A3 wait counter so the ROM drives data only when the CPU expects it. Fix state-machine transition helpers in Intel 4002 and 4003 to capture the old state before processing events, preventing incorrect exit/entry callbacks. Add non-propagating pin driver methods to prevent deadlocks caused by bidirectional pin connections during signal propagation. Add Fibonacci integration test and opcode fix plan document. Update all README files to reflect the new instruction set.
1336 lines
47 KiB
Rust
1336 lines
47 KiB
Rust
use parking_lot::Mutex;
|
|
use rusty_emu::component::{BaseComponent, Component};
|
|
use rusty_emu::components::common::intel_400x::*;
|
|
use rusty_emu::components::memory::intel_4001::{Intel4001, IoDirection, IoMode};
|
|
use rusty_emu::pin::{Pin, PinValue};
|
|
use std::any::Any;
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
use std::time::Instant;
|
|
|
|
// Mock component for testing trait implementations
|
|
#[derive(Debug)]
|
|
struct MockComponent {}
|
|
|
|
impl MockComponent {
|
|
fn new() -> Self {
|
|
Self {}
|
|
}
|
|
}
|
|
|
|
impl Component for MockComponent {
|
|
fn name(&self) -> String {
|
|
"MockComponent".to_string()
|
|
}
|
|
|
|
fn pins(&self) -> HashMap<String, Arc<Mutex<rusty_emu::pin::Pin>>> {
|
|
HashMap::new()
|
|
}
|
|
|
|
fn get_pin(&self, name: &str) -> Result<Arc<Mutex<rusty_emu::pin::Pin>>, String> {
|
|
// For testing, we'll return an error since we don't have real pins
|
|
Err(format!("Mock pin not implemented for {}", name))
|
|
}
|
|
|
|
fn update(&mut self) {
|
|
// No-op for mock
|
|
}
|
|
|
|
fn run(&mut self) {
|
|
// No-op for mock
|
|
}
|
|
|
|
fn stop(&mut self) {
|
|
// No-op for mock
|
|
}
|
|
|
|
fn is_running(&self) -> bool {
|
|
false
|
|
}
|
|
|
|
fn set_running(&mut self, _running: bool) {
|
|
// No-op for mock
|
|
}
|
|
|
|
fn as_any(&self) -> &dyn Any {
|
|
self
|
|
}
|
|
|
|
fn as_any_mut(&mut self) -> &mut dyn Any {
|
|
self
|
|
}
|
|
}
|
|
|
|
impl Intel400xAddressHandling for MockComponent {
|
|
fn get_base(&self) -> &BaseComponent {
|
|
unimplemented!("MockComponent doesn't contain BaseComponent")
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
|
|
#[test]
|
|
fn test_timing_constants() {
|
|
// Test that timing constants have expected values
|
|
assert_eq!(
|
|
TimingConstants::DEFAULT_ACCESS_TIME,
|
|
Duration::from_nanos(500)
|
|
);
|
|
assert_eq!(TimingConstants::FAST_ACCESS_TIME, Duration::from_nanos(200));
|
|
assert_eq!(TimingConstants::ADDRESS_SETUP, Duration::from_nanos(100));
|
|
assert_eq!(TimingConstants::DATA_VALID, Duration::from_nanos(200));
|
|
}
|
|
|
|
#[test]
|
|
fn test_assemble_full_address() {
|
|
// Create a mock trait implementation for testing
|
|
struct TestAddressHandler {}
|
|
|
|
impl TestAddressHandler {
|
|
fn new() -> Self {
|
|
Self {}
|
|
}
|
|
}
|
|
|
|
impl Intel400xAddressHandling for TestAddressHandler {
|
|
fn get_base(&self) -> &BaseComponent {
|
|
unimplemented!("TestAddressHandler doesn't contain BaseComponent")
|
|
}
|
|
}
|
|
|
|
let handler = TestAddressHandler::new();
|
|
|
|
// Test address assembly with valid nibbles
|
|
assert_eq!(
|
|
handler.assemble_full_address(Some(0x0F), Some(0x03)),
|
|
Some(0xF3)
|
|
);
|
|
assert_eq!(
|
|
handler.assemble_full_address(Some(0x02), Some(0x04)),
|
|
Some(0x24)
|
|
);
|
|
assert_eq!(
|
|
handler.assemble_full_address(Some(0x00), Some(0x00)),
|
|
Some(0x00)
|
|
);
|
|
assert_eq!(
|
|
handler.assemble_full_address(Some(0x0F), Some(0x0F)),
|
|
Some(0xFF)
|
|
);
|
|
|
|
// Test with missing nibbles
|
|
assert_eq!(handler.assemble_full_address(None, Some(0x23)), None);
|
|
assert_eq!(handler.assemble_full_address(Some(0x0F), None), None);
|
|
assert_eq!(handler.assemble_full_address(None, None), None);
|
|
}
|
|
|
|
#[test]
|
|
fn test_timing_state_machine() {
|
|
// Test state machine properties
|
|
let idle_state = TimingState::Idle;
|
|
let address_state = TimingState::AddressPhase;
|
|
let wait_state = TimingState::WaitLatency;
|
|
let drive_state = TimingState::DriveData;
|
|
|
|
// Test state queries
|
|
assert!(idle_state.is_idle());
|
|
assert!(!idle_state.is_address_phase());
|
|
assert!(!idle_state.is_waiting_latency());
|
|
assert!(!idle_state.is_driving_data());
|
|
|
|
assert!(!address_state.is_idle());
|
|
assert!(address_state.is_address_phase());
|
|
assert!(!address_state.is_waiting_latency());
|
|
assert!(!address_state.is_driving_data());
|
|
|
|
assert!(!wait_state.is_idle());
|
|
assert!(!wait_state.is_address_phase());
|
|
assert!(wait_state.is_waiting_latency());
|
|
assert!(!wait_state.is_driving_data());
|
|
|
|
assert!(!drive_state.is_idle());
|
|
assert!(!drive_state.is_address_phase());
|
|
assert!(!drive_state.is_waiting_latency());
|
|
assert!(drive_state.is_driving_data());
|
|
}
|
|
|
|
#[test]
|
|
fn test_memory_state_conversions() {
|
|
// Test conversions between MemoryState and TimingState
|
|
let mem_idle = MemoryState::Idle;
|
|
let timing_idle: TimingState = mem_idle.into();
|
|
assert_eq!(timing_idle, TimingState::Idle);
|
|
|
|
let mem_address = MemoryState::AddressPhase;
|
|
let timing_address: TimingState = mem_address.into();
|
|
assert_eq!(timing_address, TimingState::AddressPhase);
|
|
|
|
let mem_wait = MemoryState::WaitLatency;
|
|
let timing_wait: TimingState = mem_wait.into();
|
|
assert_eq!(timing_wait, TimingState::WaitLatency);
|
|
|
|
let mem_drive = MemoryState::DriveData;
|
|
let timing_drive: TimingState = mem_drive.into();
|
|
assert_eq!(timing_drive, TimingState::DriveData);
|
|
|
|
// Test reverse conversions
|
|
let back_to_mem: MemoryState = timing_idle.into();
|
|
assert_eq!(back_to_mem, MemoryState::Idle);
|
|
}
|
|
|
|
#[test]
|
|
fn test_ram_state_conversions() {
|
|
// Test conversions between RamState and TimingState
|
|
let ram_idle = RamState::Idle;
|
|
let timing_idle: TimingState = ram_idle.into();
|
|
assert_eq!(timing_idle, TimingState::Idle);
|
|
|
|
let ram_read = RamState::ReadData;
|
|
let timing_drive: TimingState = ram_read.into();
|
|
assert_eq!(timing_drive, TimingState::DriveData);
|
|
|
|
let ram_write = RamState::WriteData;
|
|
let timing_drive: TimingState = ram_write.into();
|
|
assert_eq!(timing_drive, TimingState::DriveData);
|
|
|
|
let ram_output = RamState::OutputPort;
|
|
let timing_drive: TimingState = ram_output.into();
|
|
assert_eq!(timing_drive, TimingState::DriveData);
|
|
}
|
|
|
|
#[test]
|
|
fn test_utils_create_driver_name() {
|
|
assert_eq!(create_driver_name("COMP1", "DATA"), "COMP1_DATA");
|
|
assert_eq!(create_driver_name("MEMORY", "ADDR"), "MEMORY_ADDR");
|
|
assert_eq!(create_driver_name("CPU", "CONTROL"), "CPU_CONTROL");
|
|
}
|
|
|
|
#[test]
|
|
fn test_data_bus_bit_operations() {
|
|
// Test bit manipulation for data bus operations
|
|
let test_values = vec![
|
|
(0x00, [false, false, false, false]),
|
|
(0x01, [true, false, false, false]),
|
|
(0x02, [false, true, false, false]),
|
|
(0x04, [false, false, true, false]),
|
|
(0x08, [false, false, false, true]),
|
|
(0x05, [true, false, true, false]),
|
|
(0x0A, [false, true, false, true]),
|
|
(0x0F, [true, true, true, true]),
|
|
];
|
|
|
|
for (value, expected_bits) in test_values {
|
|
// Test bit extraction (reading from bus)
|
|
for i in 0..4 {
|
|
let bit_value = (value >> i) & 1;
|
|
assert_eq!(bit_value == 1, expected_bits[i as usize]);
|
|
}
|
|
|
|
// Test bit setting (writing to bus)
|
|
let mut reconstructed = 0;
|
|
for i in 0..4 {
|
|
if expected_bits[i as usize] {
|
|
reconstructed |= 1 << i;
|
|
}
|
|
}
|
|
assert_eq!(value, reconstructed);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_address_latching_logic() {
|
|
// Test the address latching algorithm
|
|
let mut high_nibble: Option<u8> = None;
|
|
let mut low_nibble: Option<u8> = None;
|
|
let mut full_address_ready = false;
|
|
let mut address_latch_time: Option<Instant> = None;
|
|
let access_time = Duration::from_nanos(100);
|
|
|
|
// Mock address handler for testing the algorithm
|
|
struct TestAddressHandler {}
|
|
|
|
impl TestAddressHandler {
|
|
fn new() -> Self {
|
|
Self {}
|
|
}
|
|
}
|
|
|
|
impl Intel400xAddressHandling for TestAddressHandler {
|
|
fn get_base(&self) -> &BaseComponent {
|
|
unimplemented!("TestAddressHandler doesn't contain BaseComponent")
|
|
}
|
|
}
|
|
|
|
let handler = TestAddressHandler::new();
|
|
|
|
// First cycle: high nibble
|
|
handler.handle_address_latching(
|
|
0x12,
|
|
&mut high_nibble,
|
|
&mut low_nibble,
|
|
&mut full_address_ready,
|
|
&mut address_latch_time,
|
|
access_time,
|
|
);
|
|
|
|
assert_eq!(high_nibble, Some(0x12));
|
|
assert_eq!(low_nibble, None);
|
|
assert_eq!(full_address_ready, false);
|
|
assert_eq!(address_latch_time, None);
|
|
|
|
// Second cycle: low nibble
|
|
handler.handle_address_latching(
|
|
0x34,
|
|
&mut high_nibble,
|
|
&mut low_nibble,
|
|
&mut full_address_ready,
|
|
&mut address_latch_time,
|
|
access_time,
|
|
);
|
|
|
|
assert_eq!(high_nibble, None); // Should be cleared
|
|
assert_eq!(low_nibble, None); // Should be cleared
|
|
assert_eq!(full_address_ready, true);
|
|
assert!(address_latch_time.is_some());
|
|
|
|
// Verify the assembled address
|
|
let assembled = handler.assemble_full_address(Some(0x02), Some(0x04));
|
|
assert_eq!(assembled, Some(0x24));
|
|
}
|
|
|
|
#[test]
|
|
fn test_latency_timing_logic() {
|
|
let access_time = Duration::from_nanos(100);
|
|
|
|
// Test with no latch time (should return false)
|
|
assert_eq!(
|
|
Intel400xAddressHandling::handle_latency_wait(
|
|
&MockComponent::new(),
|
|
&None,
|
|
access_time
|
|
),
|
|
false
|
|
);
|
|
|
|
// Test with recent latch time - need to ensure enough time has elapsed
|
|
// Create the latch time, then sleep to guarantee elapsed >= access_time
|
|
let recent_time = Some(Instant::now());
|
|
thread::sleep(Duration::from_micros(1));
|
|
assert_eq!(
|
|
Intel400xAddressHandling::handle_latency_wait(
|
|
&MockComponent::new(),
|
|
&recent_time,
|
|
access_time
|
|
),
|
|
true
|
|
);
|
|
|
|
// Note: Testing with elapsed time would require sleeping or mocking time
|
|
// This demonstrates the logic structure is correct
|
|
}
|
|
|
|
#[test]
|
|
fn test_intel4001_integration_debugging() {
|
|
println!("=== Intel 4001 Integration Test: Setup ===");
|
|
let mut rom = Intel4001::new_with_access_time("TestROM".to_string(), 1); // 1ns for fast testing
|
|
rom.base.set_running(true);
|
|
|
|
// Load test data: simple arithmetic sequence
|
|
let test_data = vec![
|
|
0x01, 0x02, 0x03, 0x04, 0x05, 0x10, 0x20, 0x30, 0x40, 0x50, 0xFF,
|
|
];
|
|
rom.load_rom_data(test_data.clone(), 0).unwrap();
|
|
|
|
// Verify test data loaded
|
|
for (i, &data) in test_data.iter().enumerate() {
|
|
assert_eq!(rom.read_rom(i as u8).unwrap(), data);
|
|
}
|
|
|
|
// Get pin references
|
|
let sync_pin = rom.get_pin("SYNC").unwrap();
|
|
let cm_pin = rom.get_pin("CM").unwrap();
|
|
let ci_pin = rom.get_pin("CI").unwrap();
|
|
let reset_pin = rom.get_pin("RESET").unwrap();
|
|
let phi1_pin = rom.get_pin("PHI1").unwrap();
|
|
let phi2_pin = rom.get_pin("PHI2").unwrap();
|
|
let d0_pin = rom.get_pin("D0").unwrap();
|
|
let d1_pin = rom.get_pin("D1").unwrap();
|
|
let d2_pin = rom.get_pin("D2").unwrap();
|
|
let d3_pin = rom.get_pin("D3").unwrap();
|
|
|
|
// Set settlement time to 0 for immediate testing
|
|
sync_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
|
cm_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
|
ci_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
|
reset_pin
|
|
.lock()
|
|
.set_settlement_time(Duration::from_nanos(0));
|
|
phi1_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
|
phi2_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
|
d0_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
|
d1_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
|
d2_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
|
d3_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
|
|
|
// Initially, all data pins should be HighZ
|
|
assert_eq!(d0_pin.lock().read(), PinValue::HighZ);
|
|
assert_eq!(d1_pin.lock().read(), PinValue::HighZ);
|
|
assert_eq!(d2_pin.lock().read(), PinValue::HighZ);
|
|
assert_eq!(d3_pin.lock().read(), PinValue::HighZ);
|
|
|
|
// Test reset (RESET is active LOW)
|
|
println!("=== Testing Reset ===");
|
|
{
|
|
let mut reset_guard = reset_pin.lock();
|
|
reset_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Assert reset
|
|
}
|
|
rom.update();
|
|
assert_eq!(rom.get_timing_state(), TimingState::Idle);
|
|
assert_eq!(rom.get_io_mode(), IoMode::Input);
|
|
|
|
// Release reset (set HIGH for normal operation)
|
|
{
|
|
let mut reset_guard = reset_pin.lock();
|
|
reset_guard.set_driver(Some("TEST".to_string()), PinValue::High); // Release reset
|
|
}
|
|
rom.update();
|
|
|
|
// Test memory read at address 0x00
|
|
println!("=== Memory Access Testing: Address 0x00 ===");
|
|
test_memory_read(
|
|
&mut rom, &sync_pin, &cm_pin, &ci_pin, &phi1_pin, &phi2_pin, &d0_pin, &d1_pin, &d2_pin,
|
|
&d3_pin, 0x00, 0x01,
|
|
);
|
|
|
|
// Test memory read at address 0xFF
|
|
// Load test data at address 0xFF specifically
|
|
// Note: Intel 4004 has a 4-bit data bus, so we can only read 4 bits at a time
|
|
// The value 0x0F will be read as 0x0F (low nibble only)
|
|
rom.load_rom_data(vec![0x0F], 0xFF).unwrap();
|
|
println!("=== Memory Access Testing: Address 0xFF ===");
|
|
test_memory_read(
|
|
&mut rom, &sync_pin, &cm_pin, &ci_pin, &phi1_pin, &phi2_pin, &d0_pin, &d1_pin, &d2_pin,
|
|
&d3_pin, 0xFF, 0x0F, // 4-bit value (low nibble only)
|
|
);
|
|
|
|
// Test I/O operations
|
|
println!("=== I/O Integration Testing ===");
|
|
// Note: I/O operations on Intel 4001 require proper clock timing and control signals.
|
|
// The handle_io_operation() method requires:
|
|
// - CM (chip_select) = HIGH
|
|
// - CI (io_select) = HIGH
|
|
// - Proper clock edges to trigger the operation
|
|
|
|
// For this test, we'll verify the direct I/O port access methods work correctly
|
|
// since full I/O timing simulation would require more complex test setup.
|
|
|
|
// Test direct I/O port write (using set_io_port API)
|
|
rom.set_io_mode(IoMode::Output);
|
|
rom.set_io_port(0, 0x05).unwrap();
|
|
assert_eq!(rom.get_io_port(0).unwrap(), 0x05);
|
|
|
|
// Test I/O port read (using get_io_port API)
|
|
rom.set_io_port(0, 0x0A).unwrap();
|
|
assert_eq!(rom.get_io_port(0).unwrap(), 0x0A);
|
|
|
|
// Test that I/O direction is set to Output when we write
|
|
assert_eq!(rom.get_io_direction(0).unwrap(), IoDirection::Output);
|
|
|
|
// Debugging features
|
|
println!("=== Debugging Features ===");
|
|
rom.debug_state_transitions("Integration Test");
|
|
|
|
// Pin state snapshots
|
|
println!(
|
|
"Pin states: SYNC: {:?}, CM: {:?}, CI: {:?}, PHI1: {:?}, PHI2: {:?}",
|
|
sync_pin.lock().read(),
|
|
cm_pin.lock().read(),
|
|
ci_pin.lock().read(),
|
|
phi1_pin.lock().read(),
|
|
phi2_pin.lock().read()
|
|
);
|
|
|
|
println!("Test completed successfully!");
|
|
}
|
|
|
|
fn test_memory_read(
|
|
rom: &mut Intel4001,
|
|
sync_pin: &Arc<Mutex<Pin>>,
|
|
cm_pin: &Arc<Mutex<Pin>>,
|
|
ci_pin: &Arc<Mutex<Pin>>,
|
|
phi1_pin: &Arc<Mutex<Pin>>,
|
|
phi2_pin: &Arc<Mutex<Pin>>,
|
|
d0_pin: &Arc<Mutex<Pin>>,
|
|
d1_pin: &Arc<Mutex<Pin>>,
|
|
d2_pin: &Arc<Mutex<Pin>>,
|
|
d3_pin: &Arc<Mutex<Pin>>,
|
|
address: u8,
|
|
expected_data: u8,
|
|
) {
|
|
// Reset to known state first (RESET is active LOW)
|
|
{
|
|
let reset_pin = rom.get_pin("RESET").unwrap();
|
|
let mut reset_guard = reset_pin.lock();
|
|
reset_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Assert reset (active LOW)
|
|
}
|
|
rom.update();
|
|
{
|
|
let reset_pin = rom.get_pin("RESET").unwrap();
|
|
let mut reset_guard = reset_pin.lock();
|
|
reset_guard.set_driver(Some("TEST".to_string()), PinValue::High); // Release reset (HIGH = normal operation)
|
|
}
|
|
rom.update();
|
|
|
|
// Set SYNC=1, CM=1, CI=0 for memory read
|
|
{
|
|
let mut sync_guard = sync_pin.lock();
|
|
let mut cm_guard = cm_pin.lock();
|
|
let mut ci_guard = ci_pin.lock();
|
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
rom.update();
|
|
|
|
// Ensure PHI2 is low initially
|
|
{
|
|
let mut phi2_guard = phi2_pin.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
rom.update();
|
|
|
|
// Ensure PHI1 is low initially
|
|
{
|
|
let mut phi1_guard = phi1_pin.lock();
|
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
rom.update();
|
|
|
|
// Start the component
|
|
rom.base.set_running(true);
|
|
|
|
// Set high nibble on data bus BEFORE PHI1 rising (ROM latches on rising edge)
|
|
rom.write_data_bus((address >> 4) & 0x0F);
|
|
|
|
// PHI1 low to high: Address phase - ROM latches high nibble on rising edge
|
|
{
|
|
let mut phi1_guard = phi1_pin.lock();
|
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
}
|
|
rom.update();
|
|
assert_eq!(rom.get_timing_state(), TimingState::AddressPhase);
|
|
|
|
// PHI1 high to low
|
|
{
|
|
let mut phi1_guard = phi1_pin.lock();
|
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
rom.update();
|
|
|
|
// Set low nibble on data bus BEFORE PHI1 rising
|
|
rom.write_data_bus(address & 0x0F);
|
|
|
|
// PHI1 low to high again: ROM latches low nibble on rising edge
|
|
{
|
|
let mut phi1_guard = phi1_pin.lock();
|
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
}
|
|
rom.update();
|
|
|
|
// After latching both nibbles, ROM should be in WaitLatency
|
|
let state = rom.get_timing_state();
|
|
assert!(
|
|
state == TimingState::WaitLatency,
|
|
"Expected WaitLatency after latching both nibbles, got {:?}",
|
|
state
|
|
);
|
|
|
|
// Release data bus from test control to allow ROM to drive it
|
|
for i in 0..4 {
|
|
let pin = rom.get_pin(&format!("D{}", i)).unwrap();
|
|
let mut pin_guard = pin.lock();
|
|
pin_guard.set_driver(Some("TEST".to_string()), PinValue::HighZ);
|
|
}
|
|
|
|
// Cycle through the a3_wait_cycles: need PHI2 rising to decrement,
|
|
// then another PHI2 rising to transition to DriveData, then another to drive data.
|
|
// IMPORTANT: Only change ONE pin per update() call to avoid spurious edge detection.
|
|
|
|
// PHI1 high to low (PHI2 stays low)
|
|
{
|
|
let mut phi1_guard = phi1_pin.lock();
|
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
rom.update();
|
|
|
|
// PHI2 low to high: decrements a3_wait_cycles
|
|
{
|
|
let mut phi2_guard = phi2_pin.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
}
|
|
rom.update();
|
|
|
|
// PHI2 high to low
|
|
{
|
|
let mut phi2_guard = phi2_pin.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
rom.update();
|
|
|
|
// PHI2 low to high: a3_wait_cycles=0, transitions WaitLatency → DriveData (no data drive yet)
|
|
{
|
|
let mut phi2_guard = phi2_pin.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
}
|
|
rom.update();
|
|
|
|
// PHI2 high to low
|
|
{
|
|
let mut phi2_guard = phi2_pin.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
rom.update();
|
|
|
|
// PHI2 low to high: NOW drives data (OPR)
|
|
{
|
|
let mut phi2_guard = phi2_pin.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
}
|
|
rom.update();
|
|
assert_eq!(rom.get_timing_state(), TimingState::DriveData);
|
|
|
|
// Wait a bit for data to be driven
|
|
thread::sleep(Duration::from_nanos(10));
|
|
|
|
// Check OPR (high nibble) on bus
|
|
let opr = rom.read_data_bus();
|
|
assert_eq!(opr, (expected_data >> 4) & 0x0F, "OPR should be high nibble of expected data");
|
|
|
|
// PHI2 high to low: toggles to OPA phase
|
|
{
|
|
let mut phi2_guard = phi2_pin.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
rom.update();
|
|
|
|
// PHI2 low to high: drives OPA (low nibble)
|
|
{
|
|
let mut phi2_guard = phi2_pin.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
}
|
|
rom.update();
|
|
|
|
// Check OPA (low nibble) on bus
|
|
let opa = rom.read_data_bus();
|
|
assert_eq!(opa, expected_data & 0x0F, "OPA should be low nibble of expected data");
|
|
|
|
// PHI2 high to low: OPA complete, return to idle
|
|
{
|
|
let mut phi2_guard = phi2_pin.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
rom.update();
|
|
assert_eq!(rom.get_timing_state(), TimingState::Idle);
|
|
|
|
// Tri-state pins
|
|
assert_eq!(d0_pin.lock().read(), PinValue::HighZ);
|
|
assert_eq!(d1_pin.lock().read(), PinValue::HighZ);
|
|
assert_eq!(d2_pin.lock().read(), PinValue::HighZ);
|
|
assert_eq!(d3_pin.lock().read(), PinValue::HighZ);
|
|
}
|
|
|
|
#[test]
|
|
fn test_minimum_access_time() {
|
|
// Test behavior at minimum access time (200ns) with comprehensive edge cases
|
|
let mut chip = Intel4001::new_with_access_time("MIN_TIME_ROM".to_string(), 200); // 200ns minimum access time
|
|
chip.base.set_running(true);
|
|
|
|
// Get pin references
|
|
let sync_pin = chip.get_pin("SYNC").unwrap();
|
|
let cm_pin = chip.get_pin("CM").unwrap();
|
|
let ci_pin = chip.get_pin("CI").unwrap();
|
|
let reset_pin = chip.get_pin("RESET").unwrap();
|
|
let phi1_pin = chip.get_pin("PHI1").unwrap();
|
|
let phi2_pin = chip.get_pin("PHI2").unwrap();
|
|
let d0_pin = chip.get_pin("D0").unwrap();
|
|
let d1_pin = chip.get_pin("D1").unwrap();
|
|
let d2_pin = chip.get_pin("D2").unwrap();
|
|
let d3_pin = chip.get_pin("D3").unwrap();
|
|
|
|
// Set settlement time to 0 for immediate testing
|
|
sync_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
|
cm_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
|
ci_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
|
reset_pin
|
|
.lock()
|
|
.set_settlement_time(Duration::from_nanos(0));
|
|
phi1_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
|
phi2_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
|
d0_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
|
d1_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
|
d2_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
|
d3_pin.lock().set_settlement_time(Duration::from_nanos(0));
|
|
|
|
// Test memory read at minimum timing
|
|
// Note: Intel 4004 has a 4-bit data bus, so we can only read 4 bits at a time
|
|
// The full 8-bit read would require M1/M2 cycles (not yet implemented)
|
|
let address = 0x12;
|
|
let expected_data = 0x04; // 4-bit value (low nibble only for single-cycle read)
|
|
|
|
// Set up ROM data
|
|
chip.load_rom_data(vec![expected_data], address).unwrap();
|
|
|
|
// Test memory read using the helper function
|
|
test_memory_read(
|
|
&mut chip,
|
|
&sync_pin,
|
|
&cm_pin,
|
|
&ci_pin,
|
|
&phi1_pin,
|
|
&phi2_pin,
|
|
&d0_pin,
|
|
&d1_pin,
|
|
&d2_pin,
|
|
&d3_pin,
|
|
address as u8,
|
|
expected_data,
|
|
);
|
|
|
|
// Verify state machine transitions correctly at minimum timing
|
|
assert_eq!(chip.get_timing_state(), TimingState::Idle);
|
|
|
|
// Test multiple consecutive reads at minimum timing
|
|
// Note: Intel 4004 has a 4-bit data bus, so we can only read 4 bits at a time
|
|
// The expected data must be a 4-bit value (0x00-0x0F)
|
|
for addr in 0x10..0x15 {
|
|
// Use 4-bit values that fit on the data bus
|
|
let expected_data = (addr & 0x0F) as u8; // Low nibble only
|
|
let test_data = vec![expected_data];
|
|
chip.load_rom_data(test_data, addr).unwrap();
|
|
test_memory_read(
|
|
&mut chip,
|
|
&sync_pin,
|
|
&cm_pin,
|
|
&ci_pin,
|
|
&phi1_pin,
|
|
&phi2_pin,
|
|
&d0_pin,
|
|
&d1_pin,
|
|
&d2_pin,
|
|
&d3_pin,
|
|
addr as u8,
|
|
expected_data,
|
|
);
|
|
}
|
|
|
|
// Enhanced edge case testing for minimum access time
|
|
|
|
// Test edge case: consecutive reads at minimum access time
|
|
// Note: Intel 4004 has a 4-bit data bus, so expected data must be 0-15
|
|
for address in 0x00..0x10 {
|
|
// Use addresses 0x00-0x0F where low nibble equals address
|
|
let expected_data = (address & 0x0F) as u8; // 4-bit value
|
|
let result1 = test_memory_read_edge_case(
|
|
&mut chip,
|
|
&sync_pin,
|
|
&cm_pin,
|
|
&ci_pin,
|
|
&phi1_pin,
|
|
&phi2_pin,
|
|
&d0_pin,
|
|
&d1_pin,
|
|
&d2_pin,
|
|
&d3_pin,
|
|
address as u16,
|
|
expected_data,
|
|
);
|
|
assert_eq!(
|
|
result1, expected_data,
|
|
"Failed consecutive read 1 at address {} at minimum access time",
|
|
address
|
|
);
|
|
}
|
|
|
|
// Test edge case: rapid address changes at minimum access time
|
|
// Note: Intel 4004 has a 4-bit data bus, so expected data must be 0-15
|
|
let test_addresses = [0, 255, 128, 1, 254, 2, 253, 3];
|
|
for &address in &test_addresses {
|
|
let expected_data = (address & 0x0F) as u8; // 4-bit value for data bus
|
|
chip.load_rom_data(vec![expected_data], address).unwrap();
|
|
let result = test_memory_read_edge_case(
|
|
&mut chip,
|
|
&sync_pin,
|
|
&cm_pin,
|
|
&ci_pin,
|
|
&phi1_pin,
|
|
&phi2_pin,
|
|
&d0_pin,
|
|
&d1_pin,
|
|
&d2_pin,
|
|
&d3_pin,
|
|
address as u16,
|
|
expected_data,
|
|
);
|
|
assert_eq!(
|
|
result, expected_data,
|
|
"Failed rapid address change test at address {} at minimum access time",
|
|
address
|
|
);
|
|
}
|
|
|
|
// Test edge case: minimum access time with I/O operations
|
|
// Note: Intel 4004 has a 4-bit data bus, so expected data must be 0-15
|
|
chip.load_rom_data(vec![0x05], 0x00).unwrap(); // Test pattern (4-bit value)
|
|
chip.load_rom_data(vec![0x0A], 0xFF).unwrap(); // Another test pattern (4-bit value)
|
|
|
|
// Test I/O pin behavior during minimum access time reads
|
|
let io_pins = ["IO0", "IO1", "IO2", "IO3"];
|
|
|
|
// Set I/O pins to output mode
|
|
for pin_name in &io_pins {
|
|
let _io_pin = chip.get_pin(pin_name).unwrap();
|
|
// Note: In the actual implementation, I/O pin mode is controlled through the chip's I/O methods
|
|
// For this test, we'll verify the I/O functionality through the chip's methods
|
|
}
|
|
|
|
// Test memory read while I/O operations are active
|
|
let result = test_memory_read_edge_case(
|
|
&mut chip, &sync_pin, &cm_pin, &ci_pin, &phi1_pin, &phi2_pin, &d0_pin, &d1_pin,
|
|
&d2_pin, &d3_pin, 0x00, 0x05,
|
|
);
|
|
assert_eq!(
|
|
result, 0x05,
|
|
"Failed memory read with active I/O at minimum access time"
|
|
);
|
|
|
|
// Test edge case: minimum access time with boundary addresses
|
|
// Note: Intel 4004 has a 4-bit data bus, so expected data must be 0-15
|
|
let boundary_addresses = [0, 1, 254, 255];
|
|
for &address in &boundary_addresses {
|
|
let expected_data = (address & 0x0F) as u8; // 4-bit value for data bus
|
|
chip.load_rom_data(vec![expected_data], address).unwrap();
|
|
let result = test_memory_read_edge_case(
|
|
&mut chip,
|
|
&sync_pin,
|
|
&cm_pin,
|
|
&ci_pin,
|
|
&phi1_pin,
|
|
&phi2_pin,
|
|
&d0_pin,
|
|
&d1_pin,
|
|
&d2_pin,
|
|
&d3_pin,
|
|
address as u16,
|
|
expected_data,
|
|
);
|
|
assert_eq!(
|
|
result, expected_data,
|
|
"Failed boundary address test at {} at minimum access time",
|
|
address
|
|
);
|
|
}
|
|
|
|
// Test state machine transitions at minimum access time
|
|
// Verify proper state progression through all phases
|
|
// Note: Intel 4004 has a 4-bit data bus, so we can only read 4 bits at a time
|
|
let mut test_chip = Intel4001::new_with_access_time("STATE_TEST_ROM".to_string(), 200);
|
|
test_chip.base.set_running(true);
|
|
test_chip.load_rom_data(vec![0x02], 0x10).unwrap(); // 4-bit value (low nibble only)
|
|
|
|
// Get pins for state machine test
|
|
let test_sync = test_chip.get_pin("SYNC").unwrap();
|
|
let test_cm = test_chip.get_pin("CM").unwrap();
|
|
let test_ci = test_chip.get_pin("CI").unwrap();
|
|
let test_phi1 = test_chip.get_pin("PHI1").unwrap();
|
|
let test_phi2 = test_chip.get_pin("PHI2").unwrap();
|
|
|
|
// Set settlement times to 0
|
|
test_sync
|
|
.lock()
|
|
.set_settlement_time(Duration::from_nanos(0));
|
|
test_cm.lock().set_settlement_time(Duration::from_nanos(0));
|
|
test_ci.lock().set_settlement_time(Duration::from_nanos(0));
|
|
test_phi1
|
|
.lock()
|
|
.set_settlement_time(Duration::from_nanos(0));
|
|
test_phi2
|
|
.lock()
|
|
.set_settlement_time(Duration::from_nanos(0));
|
|
|
|
// Initialize PHI2 to Low to ensure proper edge detection
|
|
{
|
|
let mut phi2_guard = test_phi2.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
test_chip.update();
|
|
|
|
// Manually step through the state machine to verify timing
|
|
// Set control pins for memory read
|
|
{
|
|
let mut sync_guard = test_sync.lock();
|
|
let mut cm_guard = test_cm.lock();
|
|
let mut ci_guard = test_ci.lock();
|
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
test_chip.update();
|
|
assert_eq!(test_chip.get_timing_state(), TimingState::Idle);
|
|
|
|
// Ensure PHI1 is low initially
|
|
{
|
|
let mut phi1_guard = test_phi1.lock();
|
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
test_chip.update();
|
|
|
|
// Start address phase - write high nibble BEFORE PHI1 rising (ROM latches on rising edge)
|
|
test_chip.write_data_bus(0x01); // High nibble
|
|
{
|
|
let mut phi1_guard = test_phi1.lock();
|
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
}
|
|
test_chip.update();
|
|
assert_eq!(test_chip.get_timing_state(), TimingState::AddressPhase);
|
|
|
|
// PHI1 falling edge
|
|
{
|
|
let mut phi1_guard = test_phi1.lock();
|
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
test_chip.update();
|
|
|
|
// Write low nibble BEFORE PHI1 rising
|
|
test_chip.write_data_bus(0x00); // Low nibble
|
|
{
|
|
let mut phi1_guard = test_phi1.lock();
|
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
}
|
|
test_chip.update();
|
|
assert_eq!(test_chip.get_timing_state(), TimingState::WaitLatency);
|
|
|
|
// Release data bus
|
|
for i in 0..4 {
|
|
let pin = test_chip.get_pin(&format!("D{}", i)).unwrap();
|
|
let mut pin_guard = pin.lock();
|
|
pin_guard.set_driver(Some("TEST".to_string()), PinValue::HighZ);
|
|
}
|
|
|
|
// Cycle through a3_wait_cycles with PHI2 edges (one pin per update)
|
|
// PHI1 high to low
|
|
{
|
|
let mut phi1_guard = test_phi1.lock();
|
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
test_chip.update();
|
|
// PHI2 low to high: a3_wait_cycles 1→0
|
|
{
|
|
let mut phi2_guard = test_phi2.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
}
|
|
test_chip.update();
|
|
// PHI2 high to low
|
|
{
|
|
let mut phi2_guard = test_phi2.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
test_chip.update();
|
|
// PHI2 low to high: WaitLatency → DriveData (no data drive)
|
|
{
|
|
let mut phi2_guard = test_phi2.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
}
|
|
test_chip.update();
|
|
// PHI2 high to low
|
|
{
|
|
let mut phi2_guard = test_phi2.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
test_chip.update();
|
|
// PHI2 low to high: drives OPR (high nibble)
|
|
{
|
|
let mut phi2_guard = test_phi2.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
}
|
|
test_chip.update();
|
|
assert_eq!(test_chip.get_timing_state(), TimingState::DriveData);
|
|
|
|
// Read OPR (high nibble) — for data 0x02, OPR = 0
|
|
let opr = test_chip.read_data_bus();
|
|
assert_eq!(opr, 0x00, "OPR should be high nibble of 0x02");
|
|
|
|
// PHI2 high to low: toggle to OPA phase
|
|
{
|
|
let mut phi2_guard = test_phi2.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
test_chip.update();
|
|
// PHI2 low to high: drives OPA (low nibble)
|
|
{
|
|
let mut phi2_guard = test_phi2.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
}
|
|
test_chip.update();
|
|
|
|
// Read OPA (low nibble) — for data 0x02, OPA = 2
|
|
let opa = test_chip.read_data_bus();
|
|
assert_eq!(opa, 0x02, "OPA should be low nibble of 0x02");
|
|
|
|
// Combine and verify
|
|
let data = (opr << 4) | opa;
|
|
assert_eq!(data, 0x02);
|
|
|
|
// Drop SYNC/CM and complete cycle
|
|
{
|
|
let mut sync_guard = test_sync.lock();
|
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
{
|
|
let mut cm_guard = test_cm.lock();
|
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
{
|
|
let mut phi2_guard = test_phi2.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
test_chip.update();
|
|
assert_eq!(test_chip.get_timing_state(), TimingState::Idle);
|
|
|
|
// Test edge case: timing violations at minimum access time
|
|
// Test what happens when we try to read too quickly
|
|
let mut fast_chip = Intel4001::new_with_access_time("FAST_VIOLATION_ROM".to_string(), 200);
|
|
fast_chip.base.set_running(true);
|
|
fast_chip.load_rom_data(vec![0x09], 0x50).unwrap(); // Use 4-bit value (0x09)
|
|
|
|
let fast_sync = fast_chip.get_pin("SYNC").unwrap();
|
|
let fast_cm = fast_chip.get_pin("CM").unwrap();
|
|
let fast_ci = fast_chip.get_pin("CI").unwrap();
|
|
let fast_phi1 = fast_chip.get_pin("PHI1").unwrap();
|
|
let fast_phi2 = fast_chip.get_pin("PHI2").unwrap();
|
|
|
|
// Set settlement times to 0
|
|
fast_sync
|
|
.lock()
|
|
.set_settlement_time(Duration::from_nanos(0));
|
|
fast_cm.lock().set_settlement_time(Duration::from_nanos(0));
|
|
fast_ci.lock().set_settlement_time(Duration::from_nanos(0));
|
|
fast_phi1
|
|
.lock()
|
|
.set_settlement_time(Duration::from_nanos(0));
|
|
fast_phi2
|
|
.lock()
|
|
.set_settlement_time(Duration::from_nanos(0));
|
|
|
|
// Initialize PHI2 to Low to ensure proper edge detection
|
|
{
|
|
let mut phi2_guard = fast_phi2.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
fast_chip.update();
|
|
|
|
// Set up for memory read
|
|
{
|
|
let mut sync_guard = fast_sync.lock();
|
|
let mut cm_guard = fast_cm.lock();
|
|
let mut ci_guard = fast_ci.lock();
|
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
fast_chip.update();
|
|
|
|
// Ensure PHI1 is low initially
|
|
{
|
|
let mut phi1_guard = fast_phi1.lock();
|
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
fast_chip.update();
|
|
|
|
// Write high nibble BEFORE PHI1 rising (ROM latches on rising edge)
|
|
fast_chip.write_data_bus(0x05); // High nibble of address 0x50
|
|
{
|
|
let mut phi1_guard = fast_phi1.lock();
|
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
}
|
|
fast_chip.update();
|
|
|
|
// PHI1 falling edge
|
|
{
|
|
let mut phi1_guard = fast_phi1.lock();
|
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
fast_chip.update();
|
|
|
|
// Write low nibble BEFORE PHI1 rising
|
|
fast_chip.write_data_bus(0x00); // Low nibble of address 0x50
|
|
{
|
|
let mut phi1_guard = fast_phi1.lock();
|
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
}
|
|
fast_chip.update();
|
|
|
|
// Should be in WaitLatency now
|
|
let state = fast_chip.get_timing_state();
|
|
assert_eq!(state, TimingState::WaitLatency, "Expected WaitLatency, got {:?}", state);
|
|
|
|
// Release data bus
|
|
for i in 0..4 {
|
|
let pin = fast_chip.get_pin(&format!("D{}", i)).unwrap();
|
|
let mut pin_guard = pin.lock();
|
|
pin_guard.set_driver(Some("TEST".to_string()), PinValue::HighZ);
|
|
}
|
|
|
|
// Cycle PHI2 to get through a3_wait_cycles and DriveData
|
|
// PHI1 high to low
|
|
{
|
|
let mut phi1_guard = fast_phi1.lock();
|
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
fast_chip.update();
|
|
// PHI2 low to high: a3_wait_cycles 1→0
|
|
{
|
|
let mut phi2_guard = fast_phi2.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
}
|
|
fast_chip.update();
|
|
// PHI2 high to low
|
|
{
|
|
let mut phi2_guard = fast_phi2.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
fast_chip.update();
|
|
// PHI2 low to high: WaitLatency → DriveData (no data)
|
|
{
|
|
let mut phi2_guard = fast_phi2.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
}
|
|
fast_chip.update();
|
|
// PHI2 high to low
|
|
{
|
|
let mut phi2_guard = fast_phi2.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
fast_chip.update();
|
|
// PHI2 low to high: drives OPR
|
|
{
|
|
let mut phi2_guard = fast_phi2.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
}
|
|
fast_chip.update();
|
|
|
|
// Read OPR (high nibble of 0x09 = 0)
|
|
let opr = fast_chip.read_data_bus();
|
|
assert_eq!(opr, 0x00, "OPR should be high nibble of 0x09");
|
|
|
|
// PHI2 high to low: toggle to OPA
|
|
{
|
|
let mut phi2_guard = fast_phi2.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
fast_chip.update();
|
|
// PHI2 low to high: drives OPA
|
|
{
|
|
let mut phi2_guard = fast_phi2.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
}
|
|
fast_chip.update();
|
|
|
|
// Read OPA (low nibble of 0x09 = 9)
|
|
let opa = fast_chip.read_data_bus();
|
|
assert_eq!(opa, 0x09, "OPA should be low nibble of 0x09");
|
|
|
|
// Verify full byte
|
|
let data = (opr << 4) | opa;
|
|
assert_eq!(data, 0x09);
|
|
}
|
|
|
|
// Helper function for edge case memory read testing
|
|
fn test_memory_read_edge_case(
|
|
chip: &mut Intel4001,
|
|
sync_pin: &Arc<Mutex<Pin>>,
|
|
cm_pin: &Arc<Mutex<Pin>>,
|
|
ci_pin: &Arc<Mutex<Pin>>,
|
|
phi1_pin: &Arc<Mutex<Pin>>,
|
|
phi2_pin: &Arc<Mutex<Pin>>,
|
|
_d0_pin: &Arc<Mutex<Pin>>,
|
|
_d1_pin: &Arc<Mutex<Pin>>,
|
|
_d2_pin: &Arc<Mutex<Pin>>,
|
|
_d3_pin: &Arc<Mutex<Pin>>,
|
|
address: u16,
|
|
expected_data: u8,
|
|
) -> u8 {
|
|
// Load the expected data into ROM at the specified address
|
|
chip.load_rom_data(vec![expected_data], address as usize)
|
|
.unwrap();
|
|
|
|
// Reset to known state
|
|
chip.perform_reset();
|
|
chip.update();
|
|
|
|
// Start the component
|
|
chip.base.set_running(true);
|
|
|
|
// Set control pins for memory read
|
|
{
|
|
let mut sync_guard = sync_pin.lock();
|
|
let mut cm_guard = cm_pin.lock();
|
|
let mut ci_guard = ci_pin.lock();
|
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
ci_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
chip.update();
|
|
|
|
// Ensure PHI2 is low initially
|
|
{
|
|
let mut phi2_guard = phi2_pin.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
chip.update();
|
|
|
|
// Ensure PHI1 is low initially
|
|
{
|
|
let mut phi1_guard = phi1_pin.lock();
|
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
chip.update();
|
|
|
|
// Set high nibble on data bus BEFORE PHI1 rising (ROM latches on rising edge)
|
|
chip.write_data_bus(((address >> 4) & 0x0F) as u8);
|
|
|
|
// PHI1 low to high: ROM latches high nibble on rising edge
|
|
{
|
|
let mut phi1_guard = phi1_pin.lock();
|
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
}
|
|
chip.update();
|
|
|
|
// PHI1 high to low
|
|
{
|
|
let mut phi1_guard = phi1_pin.lock();
|
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
chip.update();
|
|
|
|
// Set low nibble on data bus BEFORE PHI1 rising
|
|
chip.write_data_bus((address & 0x0F) as u8);
|
|
|
|
// PHI1 low to high: ROM latches low nibble, address complete → WaitLatency
|
|
{
|
|
let mut phi1_guard = phi1_pin.lock();
|
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
}
|
|
chip.update();
|
|
|
|
// Should be in WaitLatency now
|
|
let state = chip.get_timing_state();
|
|
assert!(
|
|
state == TimingState::WaitLatency,
|
|
"Expected WaitLatency after latching both nibbles, got {:?}",
|
|
state
|
|
);
|
|
|
|
// Release data bus from test control to allow ROM to drive it
|
|
for i in 0..4 {
|
|
let pin = chip.get_pin(&format!("D{}", i)).unwrap();
|
|
let mut pin_guard = pin.lock();
|
|
pin_guard.set_driver(Some("TEST".to_string()), PinValue::HighZ);
|
|
}
|
|
|
|
// Cycle through a3_wait_cycles: need multiple PHI2 rising edges
|
|
// IMPORTANT: Only change ONE pin per update() call to avoid spurious edge detection.
|
|
// The ROM detects edges by comparing current vs previous pin values.
|
|
|
|
// Step 1: PHI1 high to low (PHI2 stays low from before)
|
|
{
|
|
let mut phi1_guard = phi1_pin.lock();
|
|
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
chip.update();
|
|
|
|
// Step 2: PHI2 low to high (a3_wait_cycles decrements)
|
|
{
|
|
let mut phi2_guard = phi2_pin.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
}
|
|
chip.update();
|
|
|
|
// Step 3: PHI2 high to low
|
|
{
|
|
let mut phi2_guard = phi2_pin.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
chip.update();
|
|
|
|
// Step 4: PHI2 low to high (a3_wait_cycles=0, transitions WaitLatency → DriveData)
|
|
{
|
|
let mut phi2_guard = phi2_pin.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
}
|
|
chip.update();
|
|
|
|
// Step 5: PHI2 high to low
|
|
{
|
|
let mut phi2_guard = phi2_pin.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
chip.update();
|
|
|
|
// Step 6: PHI2 low to high: first data drive = OPR (high nibble)
|
|
// After step 4 transitioned to DriveData, this is the first PHI2 rising
|
|
// where the ROM actually drives data (nibble_phase=false = OPR)
|
|
{
|
|
let mut phi2_guard = phi2_pin.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
}
|
|
chip.update();
|
|
|
|
// Read OPR (high nibble) from data bus
|
|
let opr = chip.read_data_bus();
|
|
|
|
// Step 7: PHI2 high to low: ROM toggles nibble phase to OPA
|
|
{
|
|
let mut phi2_guard = phi2_pin.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
chip.update();
|
|
|
|
// Step 8: PHI2 low to high: second data drive = OPA (low nibble)
|
|
{
|
|
let mut phi2_guard = phi2_pin.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
|
}
|
|
chip.update();
|
|
|
|
// Read OPA (low nibble) from data bus
|
|
let opa = chip.read_data_bus();
|
|
|
|
// Drop SYNC/CM to prevent ROM from re-triggering
|
|
{
|
|
let mut sync_guard = sync_pin.lock();
|
|
sync_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
{
|
|
let mut cm_guard = cm_pin.lock();
|
|
cm_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
|
|
// PHI2 high to low: ROM returns to Idle
|
|
{
|
|
let mut phi2_guard = phi2_pin.lock();
|
|
phi2_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
|
}
|
|
chip.update();
|
|
assert_eq!(chip.get_timing_state(), TimingState::Idle);
|
|
|
|
// Combine OPR and OPA to form the full byte
|
|
let data = (opr << 4) | opa;
|
|
assert_eq!(data, expected_data);
|
|
|
|
data
|
|
}
|
|
}
|