Fix Intel 4004 CPU instruction set and improve MCS-4 timing accuracy
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.
This commit is contained in:
parent
54144d10e9
commit
6a335a15d4
14 changed files with 4125 additions and 1062 deletions
|
|
@ -316,10 +316,10 @@ mod tests {
|
|||
false
|
||||
);
|
||||
|
||||
thread::sleep(Duration::from_micros(1));
|
||||
|
||||
// Test with recent latch time (should return false if not enough time elapsed)
|
||||
// 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(),
|
||||
|
|
@ -513,7 +513,10 @@ mod tests {
|
|||
// Start the component
|
||||
rom.base.set_running(true);
|
||||
|
||||
// PHI1 low to high: Address phase for high nibble
|
||||
// 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);
|
||||
|
|
@ -521,61 +524,78 @@ mod tests {
|
|||
rom.update();
|
||||
assert_eq!(rom.get_timing_state(), TimingState::AddressPhase);
|
||||
|
||||
// Set high nibble on data bus
|
||||
rom.write_data_bus((address >> 4) & 0x0F);
|
||||
rom.update();
|
||||
|
||||
// PHI1 high to low: Complete address phase for high nibble
|
||||
// PHI1 high to low
|
||||
{
|
||||
let mut phi1_guard = phi1_pin.lock();
|
||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||
}
|
||||
rom.update();
|
||||
|
||||
// PHI1 low to high again: Address phase for low nibble
|
||||
// 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();
|
||||
|
||||
// Set low nibble on data bus
|
||||
rom.write_data_bus(address & 0x0F);
|
||||
rom.update();
|
||||
|
||||
// PHI1 high to low: Complete address phase
|
||||
{
|
||||
let mut phi1_guard = phi1_pin.lock();
|
||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||
}
|
||||
rom.update();
|
||||
|
||||
// Check if we're in WaitLatency or DriveData state
|
||||
// With very small access times, we might transition directly to DriveData
|
||||
// After latching both nibbles, ROM should be in WaitLatency
|
||||
let state = rom.get_timing_state();
|
||||
assert!(
|
||||
state == TimingState::WaitLatency || state == TimingState::DriveData,
|
||||
"Expected WaitLatency or DriveData, got {:?}",
|
||||
state == TimingState::WaitLatency,
|
||||
"Expected WaitLatency after latching both nibbles, got {:?}",
|
||||
state
|
||||
);
|
||||
|
||||
// Wait for latency if needed
|
||||
if state == TimingState::WaitLatency {
|
||||
// Get the actual access time from the ROM
|
||||
let access_time_ns = rom.get_access_time();
|
||||
thread::sleep(Duration::from_nanos(access_time_ns + 10)); // Add small buffer
|
||||
rom.update(); // Update after waiting to check latency
|
||||
}
|
||||
|
||||
// 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: Data phase
|
||||
// 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);
|
||||
|
|
@ -585,13 +605,30 @@ mod tests {
|
|||
|
||||
// Wait a bit for data to be driven
|
||||
thread::sleep(Duration::from_nanos(10));
|
||||
rom.update(); // Additional update to ensure data is driven on the bus
|
||||
|
||||
// Check data on bus
|
||||
let data = rom.read_data_bus();
|
||||
assert_eq!(data, expected_data);
|
||||
// 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: Cleanup
|
||||
// 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);
|
||||
|
|
@ -851,7 +888,8 @@ mod tests {
|
|||
}
|
||||
test_chip.update();
|
||||
|
||||
// Start address phase - first PHI1 cycle for high nibble
|
||||
// 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);
|
||||
|
|
@ -859,41 +897,61 @@ mod tests {
|
|||
test_chip.update();
|
||||
assert_eq!(test_chip.get_timing_state(), TimingState::AddressPhase);
|
||||
|
||||
// Apply high nibble
|
||||
test_chip.write_data_bus(0x01); // High nibble
|
||||
test_chip.update();
|
||||
|
||||
// PHI1 falling edge - latch high nibble
|
||||
// PHI1 falling edge
|
||||
{
|
||||
let mut phi1_guard = test_phi1.lock();
|
||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||
}
|
||||
test_chip.update();
|
||||
assert_eq!(test_chip.get_timing_state(), TimingState::AddressPhase); // Still in address phase
|
||||
|
||||
// Second PHI1 cycle for low nibble
|
||||
// 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);
|
||||
|
||||
// Apply low nibble
|
||||
test_chip.write_data_bus(0x00); // Low nibble
|
||||
test_chip.update();
|
||||
// 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);
|
||||
}
|
||||
|
||||
// PHI1 falling edge - latch low nibble and complete address
|
||||
// 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();
|
||||
assert_eq!(test_chip.get_timing_state(), TimingState::WaitLatency);
|
||||
|
||||
// Wait for minimum latency (200ns)
|
||||
thread::sleep(Duration::from_nanos(210)); // Slightly more than minimum
|
||||
|
||||
// PHI2 high: Data phase
|
||||
// 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);
|
||||
|
|
@ -901,11 +959,40 @@ mod tests {
|
|||
test_chip.update();
|
||||
assert_eq!(test_chip.get_timing_state(), TimingState::DriveData);
|
||||
|
||||
// Verify data is driven correctly (4-bit value)
|
||||
let data = test_chip.read_data_bus();
|
||||
// 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);
|
||||
|
||||
// Complete cycle
|
||||
// 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);
|
||||
|
|
@ -963,71 +1050,101 @@ mod tests {
|
|||
}
|
||||
fast_chip.update();
|
||||
|
||||
// First PHI1 cycle - latch high nibble
|
||||
{
|
||||
let mut phi1_guard = fast_phi1.lock();
|
||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||
}
|
||||
fast_chip.update();
|
||||
|
||||
// Apply high nibble
|
||||
// Write high nibble BEFORE PHI1 rising (ROM latches on rising edge)
|
||||
fast_chip.write_data_bus(0x05); // High nibble of address 0x50
|
||||
fast_chip.update();
|
||||
|
||||
// PHI1 falling edge - latch high nibble
|
||||
{
|
||||
let mut phi1_guard = fast_phi1.lock();
|
||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||
}
|
||||
fast_chip.update();
|
||||
|
||||
// Second PHI1 cycle - latch low nibble
|
||||
{
|
||||
let mut phi1_guard = fast_phi1.lock();
|
||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||
}
|
||||
fast_chip.update();
|
||||
|
||||
// Apply low nibble
|
||||
fast_chip.write_data_bus(0x00); // Low nibble of address 0x50
|
||||
fast_chip.update();
|
||||
|
||||
// PHI1 falling edge - latch low nibble and complete address
|
||||
// PHI1 falling edge
|
||||
{
|
||||
let mut phi1_guard = fast_phi1.lock();
|
||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||
}
|
||||
fast_chip.update();
|
||||
|
||||
// At this point, the state should be WaitLatency or DriveData
|
||||
let state = fast_chip.get_timing_state();
|
||||
assert!(
|
||||
state == TimingState::WaitLatency || state == TimingState::DriveData,
|
||||
"Expected WaitLatency or DriveData, got {:?}",
|
||||
state
|
||||
);
|
||||
// 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();
|
||||
|
||||
// Release data bus from test control to allow ROM to drive it
|
||||
// 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);
|
||||
}
|
||||
fast_chip.update();
|
||||
|
||||
// Try to read data - PHI2 rising edge triggers data driving if latency elapsed
|
||||
// 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();
|
||||
|
||||
// Wait for proper timing and then verify data
|
||||
thread::sleep(Duration::from_nanos(210));
|
||||
// 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();
|
||||
|
||||
// Now data should be valid (4-bit value)
|
||||
let data = fast_chip.read_data_bus();
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
|
@ -1082,88 +1199,127 @@ mod tests {
|
|||
}
|
||||
chip.update();
|
||||
|
||||
// PHI1 low to high: Address phase for high nibble
|
||||
{
|
||||
let mut phi1_guard = phi1_pin.lock();
|
||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::High);
|
||||
}
|
||||
chip.update();
|
||||
|
||||
// Set high nibble on data bus
|
||||
// Set high nibble on data bus BEFORE PHI1 rising (ROM latches on rising edge)
|
||||
chip.write_data_bus(((address >> 4) & 0x0F) as u8);
|
||||
chip.update();
|
||||
|
||||
// PHI1 high to low: Complete address phase for high nibble
|
||||
{
|
||||
let mut phi1_guard = phi1_pin.lock();
|
||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||
}
|
||||
chip.update();
|
||||
|
||||
// PHI1 low to high again: Address phase for low nibble
|
||||
// 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();
|
||||
|
||||
// Set low nibble on data bus
|
||||
chip.write_data_bus((address & 0x0F) as u8);
|
||||
chip.update();
|
||||
|
||||
// PHI1 high to low: Complete address phase
|
||||
// PHI1 high to low
|
||||
{
|
||||
let mut phi1_guard = phi1_pin.lock();
|
||||
phi1_guard.set_driver(Some("TEST".to_string()), PinValue::Low);
|
||||
}
|
||||
chip.update();
|
||||
|
||||
// Wait for the address to be fully latched and state to transition
|
||||
thread::sleep(Duration::from_nanos(10));
|
||||
// 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();
|
||||
|
||||
// Check if we're in WaitLatency or DriveData state
|
||||
// With very small access times, we might transition directly to DriveData
|
||||
// Should be in WaitLatency now
|
||||
let state = chip.get_timing_state();
|
||||
assert!(
|
||||
state == TimingState::WaitLatency || state == TimingState::DriveData,
|
||||
"Expected WaitLatency or DriveData, got {:?}",
|
||||
state == TimingState::WaitLatency,
|
||||
"Expected WaitLatency after latching both nibbles, got {:?}",
|
||||
state
|
||||
);
|
||||
|
||||
// Wait for latency if needed
|
||||
let current_state = chip.get_timing_state();
|
||||
if current_state == TimingState::WaitLatency {
|
||||
// Get the actual access time from the ROM
|
||||
let access_time_ns = chip.get_access_time();
|
||||
thread::sleep(Duration::from_nanos(access_time_ns + 10)); // Add small buffer
|
||||
chip.update(); // Update after waiting to check latency
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
// PHI2 low to high: Data phase
|
||||
// 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();
|
||||
assert_eq!(chip.get_timing_state(), TimingState::DriveData);
|
||||
|
||||
// Wait a bit for data to be driven
|
||||
thread::sleep(Duration::from_nanos(10));
|
||||
chip.update(); // Additional update to ensure data is driven on the bus
|
||||
// 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();
|
||||
|
||||
// Check data on bus
|
||||
let data = chip.read_data_bus();
|
||||
// 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();
|
||||
|
||||
// PHI2 high to low: Cleanup
|
||||
// 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);
|
||||
|
|
@ -1171,6 +1327,10 @@ mod tests {
|
|||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue