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
|
|
@ -8,23 +8,35 @@ written in Rust.
|
|||
```
|
||||
src/
|
||||
├── lib.rs # Library root and main exports
|
||||
├── main.rs # Binary entry point
|
||||
├── main.rs # Binary entry point (GUI, console, CLI)
|
||||
├── component.rs # Core component trait definitions
|
||||
├── connection.rs # Component interconnection
|
||||
├── pin.rs # Pin and signal abstractions
|
||||
├── connection.rs # Component interconnection
|
||||
├── bus.rs # Data bus implementations
|
||||
├── types.rs # Common type definitions
|
||||
├── components/ # Hardware component implementations
|
||||
│ ├── mod.rs # Component module exports
|
||||
│ ├── common/ # Shared functionality
|
||||
│ │ ├── mod.rs # Common exports
|
||||
│ │ └── intel_400x.rs # Intel 400x common traits
|
||||
│ ├── cpu/ # CPU implementations
|
||||
│ ├── memory/ # Memory component implementations
|
||||
│ └── clock/ # Clock generation components
|
||||
└── systems/ # Complete system integrations
|
||||
├── mod.rs # System module exports
|
||||
└── intel_mcs_4.rs # Intel MCS-4 system
|
||||
├── types.rs # Common type definitions (U12 address type)
|
||||
├── system_config.rs # JSON-based system configuration
|
||||
├── gui/ # Graphical user interface
|
||||
│ ├── mod.rs # GUI module exports
|
||||
│ ├── components.rs # GUI component implementations
|
||||
│ └── state.rs # GUI state management
|
||||
└── components/ # Hardware component implementations
|
||||
├── mod.rs # Component module exports
|
||||
├── common/ # Shared Intel 400x functionality
|
||||
│ ├── mod.rs # Common exports
|
||||
│ └── intel_400x.rs # Intel 400x unified state machine and traits
|
||||
├── cpu/ # CPU implementations
|
||||
│ ├── mod.rs # CPU module exports
|
||||
│ ├── intel_4004.rs # Intel 4004 CPU (complete instruction set)
|
||||
│ ├── mos_6502.rs # MOS 6502 CPU (placeholder)
|
||||
│ └── wdc_65c02.rs # WDC 65C02 CPU (placeholder)
|
||||
├── memory/ # Memory component implementations
|
||||
│ ├── mod.rs # Memory module exports
|
||||
│ ├── intel_4001.rs # Intel 4001 ROM with I/O ports
|
||||
│ ├── intel_4002.rs # Intel 4002 RAM with output ports
|
||||
│ └── intel_4003.rs # Intel 4003 shift register
|
||||
└── clock/ # Clock generation components
|
||||
├── mod.rs # Clock module exports
|
||||
├── generic_clock.rs # Generic clock generator
|
||||
└── two_phase_clock.rs # Two-phase clock for Intel 400x
|
||||
```
|
||||
|
||||
## Core Abstractions
|
||||
|
|
@ -71,8 +83,6 @@ The system uses a two-phase clock architecture (Φ1, Φ2) that matches the Intel
|
|||
- **Intel4001**: 256-byte ROM with 4-bit I/O ports
|
||||
- **Intel4002**: 320-bit RAM with 4-bit output ports
|
||||
- **Intel4003**: 10-bit shift register
|
||||
- **GenericRAM**: Generic RAM implementation
|
||||
- **GenericROM**: Generic ROM implementation
|
||||
|
||||
### Common Functionality (`src/components/common/`)
|
||||
|
||||
|
|
@ -88,18 +98,18 @@ The system uses a two-phase clock architecture (Φ1, Φ2) that matches the Intel
|
|||
|
||||
- **GenericClock**: Configurable clock generator
|
||||
|
||||
## System Integration (`src/systems/`)
|
||||
## System Configuration (`src/system_config.rs`)
|
||||
|
||||
### Intel MCS-4 System
|
||||
### JSON-Based System Configuration
|
||||
|
||||
Complete Intel MCS-4 (Micro Computer System) implementation featuring:
|
||||
Complete system configuration via JSON files featuring:
|
||||
|
||||
- Intel 4004 CPU
|
||||
- Intel 4001 ROM units
|
||||
- Intel 4002 RAM units
|
||||
- Intel 4003 shift registers
|
||||
- System clock and timing
|
||||
- Component interconnection
|
||||
- Intel 4004 CPU with configurable clock speed
|
||||
- Intel 4001 ROM units with programmable contents
|
||||
- Intel 4002 RAM units with bank selection
|
||||
- Intel 4003 shift registers for I/O expansion
|
||||
- System clock and timing configuration
|
||||
- Automatic component interconnection via pin wiring
|
||||
|
||||
## Key Features
|
||||
|
||||
|
|
@ -145,17 +155,15 @@ rom.update(); // Process one clock cycle
|
|||
### System Integration
|
||||
|
||||
```rust
|
||||
use rusty_emu::systems::intel_mcs_4::IntelMCS4;
|
||||
use rusty_emu::system_config::SystemFactory;
|
||||
|
||||
// Create complete MCS-4 system
|
||||
let system = IntelMCS4::new();
|
||||
|
||||
// Load program into ROM
|
||||
system.load_program(0, & program_data) ?;
|
||||
// Create system from JSON configuration
|
||||
let factory = SystemFactory::new();
|
||||
let system = factory.create_from_json("configs/mcs4_basic.json") ?;
|
||||
|
||||
// Get system information
|
||||
let info = system.get_system_info();
|
||||
println!("Loaded system: {} with {} components", info.name, info.component_count);
|
||||
println!("Created system: {} with {} components", info.name, info.component_count);
|
||||
|
||||
// Run simulation (in a real application)
|
||||
// system.run();
|
||||
|
|
|
|||
|
|
@ -74,8 +74,9 @@ pub enum Intel400xUnifiedState {
|
|||
OutputPort, // Intel 4002 (RAM) specific
|
||||
ShiftData, // Intel 4003 (Shift Register) specific
|
||||
LoadData, // Intel 4003 (Shift Register) specific
|
||||
FetchInstruction, // Intel 4004 (CPU) specific
|
||||
ExecuteInstruction, // Intel 4004 (CPU) specific
|
||||
FetchByte1, // Intel 4004 (CPU) specific — fetching first byte
|
||||
FetchByte2, // Intel 4004 (CPU) specific — fetching second byte of 2-byte instruction
|
||||
Execute, // Intel 4004 (CPU) specific — executing decoded instruction
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -292,7 +293,7 @@ The unified state machine operates by:
|
|||
- **Intel 4001 (ROM)**: Transitions to DriveData to provide read-only data
|
||||
- **Intel 4002 (RAM)**: Transitions to ReadData, WriteData, or OutputPort based on operation
|
||||
- **Intel 4003 (Shift Register)**: Ignores address transmission, goes directly to ShiftData
|
||||
- **Intel 4004 (CPU)**: Uses FetchInstruction and ExecuteInstruction states
|
||||
- **Intel 4004 (CPU)**: Uses FetchByte1, FetchByte2, and Execute states for instruction processing
|
||||
|
||||
## Timing Constants
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -38,6 +38,16 @@ pub struct Intel4001 {
|
|||
// Clock edge detection
|
||||
prev_phi1: PinValue, // Previous Φ1 clock state for edge detection
|
||||
prev_phi2: PinValue, // Previous Φ2 clock state for edge detection
|
||||
// M1/M2 data phase tracking
|
||||
// M1: ROM drives instruction high nibble (OPR, bits 7-4)
|
||||
// M2: ROM drives instruction low nibble (OPA, bits 3-0)
|
||||
data_nibble_phase: bool, // false = OPR (high nibble), true = OPA (low nibble)
|
||||
// A3 wait counter: after latching both address nibbles (A1+A2), the ROM
|
||||
// must wait one more full clock cycle for the CPU's A3 cycle before driving data.
|
||||
// This counter tracks how many PHI2 rising edges to wait in WaitLatency.
|
||||
a3_wait_cycles: u8,
|
||||
// Track whether data was actually driven during this PHI2 high period
|
||||
data_driven_this_cycle: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
|
|
@ -102,6 +112,10 @@ impl Intel400xResetHandling for Intel4001 {
|
|||
// This is critical for tests that reset the chip between operations
|
||||
self.prev_phi1 = PinValue::Low;
|
||||
self.prev_phi2 = PinValue::Low;
|
||||
|
||||
// Reset A3 wait counter and data nibble phase
|
||||
self.a3_wait_cycles = 0;
|
||||
self.data_nibble_phase = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -180,10 +194,19 @@ impl Intel400xComponentBehavior for Intel4001 {
|
|||
Intel400xUnifiedState::Idle => {
|
||||
// Entry actions for Idle state
|
||||
self.tri_state_data_bus();
|
||||
// Reset address state for next fetch
|
||||
self.state_machine.set_address_high_nibble(None);
|
||||
self.state_machine.set_address_low_nibble(None);
|
||||
// Reset data driving state for next fetch
|
||||
self.data_driven_this_cycle = false;
|
||||
self.data_nibble_phase = false; // Reset to OPR phase
|
||||
}
|
||||
Intel400xUnifiedState::AddressPhase => {
|
||||
// Entry actions for AddressPhase state
|
||||
// Address latching will happen in execute_state_logic
|
||||
// Reset address state to ensure clean latch
|
||||
self.state_machine.set_address_high_nibble(None);
|
||||
self.state_machine.set_address_low_nibble(None);
|
||||
}
|
||||
Intel400xUnifiedState::WaitLatency => {
|
||||
// Entry actions for WaitLatency state
|
||||
|
|
@ -327,6 +350,9 @@ impl Intel4001 {
|
|||
),
|
||||
prev_phi1: PinValue::Low,
|
||||
prev_phi2: PinValue::Low,
|
||||
data_nibble_phase: false, // Start with OPR (high nibble)
|
||||
a3_wait_cycles: 0, // No A3 wait initially
|
||||
data_driven_this_cycle: false, // No data driven yet
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -401,7 +427,7 @@ impl Intel4001 {
|
|||
/// Intel 4004 Address Timing (CPU drives address during PHI1):
|
||||
/// - A1 cycle: CPU drives PC high nibble on PHI1 rising
|
||||
/// - A2 cycle: CPU drives PC low nibble on PHI1 rising
|
||||
/// - ROM latches each nibble on PHI1 FALLING edge (after data is stable)
|
||||
/// - ROM latches each nibble on PHI1 RISING edge (CPU has just driven the address)
|
||||
fn handle_phi1_rising(&mut self) {
|
||||
// Handle system reset first (highest priority)
|
||||
self.handle_reset("RESET");
|
||||
|
|
@ -421,8 +447,21 @@ impl Intel4001 {
|
|||
if self.state_machine.get_state() == Intel400xUnifiedState::Idle {
|
||||
self.process_chip_select();
|
||||
}
|
||||
// NOTE: Address nibble latching moved to PHI1 falling edge
|
||||
// This gives the data bus time to stabilize after CPU drives it on PHI1 rising
|
||||
}
|
||||
|
||||
// Latch address nibble on PHI1 rising edge
|
||||
// The CPU drives the address on PHI1 rising, and since CPU update runs
|
||||
// before ROM update, the address is already on the bus at this point.
|
||||
if self.state_machine.get_state() == Intel400xUnifiedState::AddressPhase {
|
||||
let nibble = self.read_data_bus() & 0x0F;
|
||||
|
||||
if self.state_machine.handle_address_nibble(nibble) {
|
||||
// Address is complete (both nibbles latched), transition to latency wait
|
||||
let _addr = self.state_machine.get_full_address().unwrap_or(0xFFFF);
|
||||
self.process_address_complete();
|
||||
// Set A3 wait counter
|
||||
self.a3_wait_cycles = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle I/O operations (higher priority than memory operations)
|
||||
|
|
@ -433,39 +472,33 @@ impl Intel4001 {
|
|||
}
|
||||
}
|
||||
|
||||
/// Handle Φ1 falling edge - Latch address nibbles
|
||||
/// Hardware: Φ1 low = CPU has finished driving address, data is stable
|
||||
/// ROM latches address nibbles on PHI1 falling edge to ensure data is stable
|
||||
/// Handle Φ1 falling edge - No specific action needed
|
||||
/// Address latching is now done on PHI1 rising edge
|
||||
fn handle_phi1_falling(&mut self) {
|
||||
// Only latch if we're in AddressPhase
|
||||
// Note: We do NOT check chip_select here because:
|
||||
// 1. Chip select was already verified when we entered AddressPhase
|
||||
// 2. CM pin may be lowered between A1 and A2 cycles (this is valid)
|
||||
// 3. Once address phase starts, we must complete both nibbles
|
||||
// 4. The io_select (CI) pin distinguishes I/O vs memory operations
|
||||
if self.state_machine.get_state() == Intel400xUnifiedState::AddressPhase {
|
||||
// Latch address nibble on PHI1 falling edge (data is now stable)
|
||||
let nibble = self.read_data_bus() & 0x0F;
|
||||
|
||||
if self.state_machine.handle_address_nibble(nibble) {
|
||||
// Address is complete (both nibbles latched), transition to latency wait
|
||||
self.process_address_complete();
|
||||
}
|
||||
}
|
||||
// Address latching moved to PHI1 rising edge
|
||||
}
|
||||
|
||||
/// Handle Φ2 rising edge - Data phase
|
||||
/// Hardware: Φ2 high = Peripherals drive bus with data
|
||||
/// Focus: Data driving operations
|
||||
fn handle_phi2_rising(&mut self) {
|
||||
// Check for latency elapsed and transition to DriveData if needed
|
||||
// Handle A3 wait cycle: after address complete, wait one full clock cycle
|
||||
// for the CPU's A3 cycle before transitioning to DriveData.
|
||||
if self.state_machine.get_state() == Intel400xUnifiedState::WaitLatency {
|
||||
if self.state_machine.is_latency_elapsed() {
|
||||
if self.a3_wait_cycles > 0 {
|
||||
// Still waiting for A3 cycle to complete
|
||||
self.a3_wait_cycles -= 1;
|
||||
} else {
|
||||
// A3 wait complete, transition to DriveData.
|
||||
// IMPORTANT: Do NOT drive data on this same edge.
|
||||
// The first data drive happens on the NEXT PHI2 rising edge.
|
||||
// This ensures the ROM's OPR/OPA phases align with the CPU's M1/M2 cycles.
|
||||
self.process_latency_elapsed();
|
||||
return; // Skip data driving on this edge
|
||||
}
|
||||
}
|
||||
|
||||
// Drive data if in DriveData state
|
||||
// Drive data if in DriveData state (only on subsequent PHI2 rising edges)
|
||||
if self.state_machine.get_state() == Intel400xUnifiedState::DriveData {
|
||||
self.handle_data_driving();
|
||||
}
|
||||
|
|
@ -478,9 +511,25 @@ impl Intel4001 {
|
|||
fn handle_phi2_falling(&mut self) {
|
||||
match self.state_machine.get_state() {
|
||||
Intel400xUnifiedState::DriveData => {
|
||||
// End of data phase - tri-state bus and return to idle
|
||||
self.tri_state_data_bus();
|
||||
self.process_operation_complete();
|
||||
if self.data_driven_this_cycle {
|
||||
// Data was actually driven this cycle - toggle nibble phase
|
||||
if !self.data_nibble_phase {
|
||||
// Just finished OPR (M1), switch to OPA (M2)
|
||||
self.data_nibble_phase = true;
|
||||
self.tri_state_data_bus();
|
||||
// Stay in DriveData for M2 cycle
|
||||
} else {
|
||||
// Just finished OPA (M2) - full 8-bit instruction sent
|
||||
// Return to idle so the next fetch can begin
|
||||
self.data_nibble_phase = false;
|
||||
self.tri_state_data_bus();
|
||||
self.process_operation_complete();
|
||||
}
|
||||
} else {
|
||||
// No data driven this cycle (e.g., just entered DriveData)
|
||||
// Don't toggle - just tri-state
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
}
|
||||
|
||||
Intel400xUnifiedState::Idle
|
||||
|
|
@ -740,7 +789,12 @@ impl Intel4001 {
|
|||
/// Handle data driving during DriveData state
|
||||
/// Hardware: ROM drives data bus when all conditions are met
|
||||
/// Data stays on bus during Φ2 high period, tri-stated on Φ2 falling edge
|
||||
/// IMPORTANT: Do NOT return to idle here after OPA - the data must stay on the bus
|
||||
/// for the CPU to read. Return to idle happens in handle_phi2_falling().
|
||||
fn handle_data_driving(&mut self) {
|
||||
// Reset the data-driven flag before each drive attempt
|
||||
self.data_driven_this_cycle = false;
|
||||
|
||||
// Once we're in DriveData state, we should drive data regardless of current control signals
|
||||
// The control signals were already checked when we started memory operation
|
||||
if self.state_machine.is_full_address_ready() {
|
||||
|
|
@ -749,8 +803,18 @@ impl Intel4001 {
|
|||
let address = self.state_machine.get_full_address().unwrap_or(0) as u16;
|
||||
if (address as usize) < self.memory.len() {
|
||||
let data = self.memory[address as usize];
|
||||
self.write_data_bus(data);
|
||||
// Note: Don't call return_to_idle() here - wait for Φ2 falling edge
|
||||
// Intel 4004 data bus is 4 bits wide. Instructions are 8 bits.
|
||||
// M1 (data_nibble_phase=false): Drive high nibble (OPR, bits 7-4)
|
||||
// M2 (data_nibble_phase=true): Drive low nibble (OPA, bits 3-0)
|
||||
let nibble = if self.data_nibble_phase {
|
||||
data & 0x0F // OPA: low nibble
|
||||
} else {
|
||||
(data >> 4) & 0x0F // OPR: high nibble
|
||||
};
|
||||
self.write_data_bus(nibble);
|
||||
self.data_driven_this_cycle = true;
|
||||
// Do NOT return to idle here! The data must persist on the bus
|
||||
// for the CPU to read. Return to idle happens in handle_phi2_falling().
|
||||
} else {
|
||||
// Invalid address, tri-state
|
||||
self.tri_state_data_bus();
|
||||
|
|
@ -763,13 +827,13 @@ impl Intel4001 {
|
|||
|
||||
/// 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;
|
||||
// Use the process_event_direct function to avoid borrowing conflicts
|
||||
let new_state = process_event_direct(&mut self.state_machine, event);
|
||||
|
||||
// Handle state entry/exit manually
|
||||
if new_state != self.state_machine.get_state() {
|
||||
self.on_state_exit(self.state_machine.get_state());
|
||||
if new_state != old_state {
|
||||
self.on_state_exit(old_state);
|
||||
self.state_machine.current_state = new_state;
|
||||
self.on_state_entry(new_state);
|
||||
}
|
||||
|
|
@ -777,42 +841,36 @@ impl Intel4001 {
|
|||
|
||||
/// 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;
|
||||
// Use the process_event_direct function to avoid borrowing conflicts
|
||||
let new_state = process_event_direct(&mut self.state_machine, event);
|
||||
|
||||
// Handle state entry/exit manually
|
||||
if new_state != self.state_machine.get_state() {
|
||||
self.on_state_exit(self.state_machine.get_state());
|
||||
self.state_machine.current_state = new_state;
|
||||
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;
|
||||
// Use the process_event_direct function to avoid borrowing conflicts
|
||||
let new_state = process_event_direct(&mut self.state_machine, event);
|
||||
|
||||
// Handle state entry/exit manually
|
||||
if new_state != self.state_machine.get_state() {
|
||||
self.on_state_exit(self.state_machine.get_state());
|
||||
self.state_machine.current_state = new_state;
|
||||
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;
|
||||
// Use the process_event_direct function to avoid borrowing conflicts
|
||||
let new_state = process_event_direct(&mut self.state_machine, event);
|
||||
|
||||
// Handle state entry/exit manually
|
||||
if new_state != self.state_machine.get_state() {
|
||||
self.on_state_exit(self.state_machine.get_state());
|
||||
self.state_machine.current_state = new_state;
|
||||
if new_state != old_state {
|
||||
self.on_state_exit(old_state);
|
||||
self.on_state_entry(new_state);
|
||||
}
|
||||
}
|
||||
|
|
@ -867,21 +925,25 @@ impl Component for Intel4001 {
|
|||
}
|
||||
|
||||
// Process clock events through component-specific handlers
|
||||
// These handlers contain the actual logic for chip select, address latching, etc.
|
||||
if phi1_rising {
|
||||
self.handle_phi1_rising();
|
||||
// IMPORTANT ORDER: Handle falling edges BEFORE rising edges.
|
||||
// When PHI1 rises and PHI2 falls simultaneously (same half-cycle),
|
||||
// we must complete the previous operation (PHI2 falling) before
|
||||
// starting a new one (PHI1 rising). This ensures the ROM returns
|
||||
// to idle from OPA completion before starting a new address phase.
|
||||
if phi2_falling {
|
||||
self.handle_phi2_falling();
|
||||
}
|
||||
|
||||
if phi1_falling {
|
||||
self.handle_phi1_falling();
|
||||
}
|
||||
|
||||
if phi2_rising {
|
||||
self.handle_phi2_rising();
|
||||
if phi1_rising {
|
||||
self.handle_phi1_rising();
|
||||
}
|
||||
|
||||
if phi2_falling {
|
||||
self.handle_phi2_falling();
|
||||
if phi2_rising {
|
||||
self.handle_phi2_rising();
|
||||
}
|
||||
|
||||
// Note: execute_state_machine() is NOT called here because:
|
||||
|
|
@ -938,6 +1000,22 @@ impl RunnableComponent for Intel4001 {}
|
|||
|
||||
// Intel 4001 specific methods
|
||||
impl Intel4001 {
|
||||
/// Debug: get current state machine state name
|
||||
pub fn debug_state(&self) -> &'static str {
|
||||
match self.state_machine.get_state() {
|
||||
Intel400xUnifiedState::Idle => "Idle",
|
||||
Intel400xUnifiedState::AddressPhase => "AddrPhase",
|
||||
Intel400xUnifiedState::WaitLatency => "WaitLatency",
|
||||
Intel400xUnifiedState::DriveData => "DriveData",
|
||||
_ => "Unknown",
|
||||
}
|
||||
}
|
||||
|
||||
/// Debug: get current data nibble phase
|
||||
pub fn debug_nibble_phase(&self) -> bool {
|
||||
self.data_nibble_phase
|
||||
}
|
||||
|
||||
/// Get ROM size in bytes
|
||||
/// Returns: Total number of bytes in ROM (256 for 4001)
|
||||
pub fn get_rom_size(&self) -> usize {
|
||||
|
|
|
|||
|
|
@ -613,42 +613,36 @@ impl Intel4002 {
|
|||
|
||||
/// 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;
|
||||
// Use the process_event_direct function to avoid borrowing conflicts
|
||||
let new_state = process_event_direct(&mut self.state_machine, event);
|
||||
|
||||
// Handle state entry/exit manually
|
||||
if new_state != self.state_machine.get_state() {
|
||||
self.on_state_exit(self.state_machine.get_state());
|
||||
self.state_machine.current_state = new_state;
|
||||
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;
|
||||
// Use the process_event_direct function to avoid borrowing conflicts
|
||||
let new_state = process_event_direct(&mut self.state_machine, event);
|
||||
|
||||
// Handle state entry/exit manually
|
||||
if new_state != self.state_machine.get_state() {
|
||||
self.on_state_exit(self.state_machine.get_state());
|
||||
self.state_machine.current_state = new_state;
|
||||
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;
|
||||
// Use the process_event_direct function to avoid borrowing conflicts
|
||||
let new_state = process_event_direct(&mut self.state_machine, event);
|
||||
|
||||
// Handle state entry/exit manually
|
||||
if new_state != self.state_machine.get_state() {
|
||||
self.on_state_exit(self.state_machine.get_state());
|
||||
self.state_machine.current_state = new_state;
|
||||
if new_state != old_state {
|
||||
self.on_state_exit(old_state);
|
||||
self.on_state_entry(new_state);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -453,56 +453,48 @@ impl Intel4003 {
|
|||
|
||||
/// 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;
|
||||
// Use process_event_direct method to avoid borrowing conflicts
|
||||
let new_state = process_event_direct(&mut self.state_machine, event);
|
||||
|
||||
// Handle state entry/exit manually
|
||||
if new_state != self.state_machine.get_state() {
|
||||
self.on_state_exit(self.state_machine.get_state());
|
||||
self.state_machine.current_state = new_state;
|
||||
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;
|
||||
// Use process_event_direct method to avoid borrowing conflicts
|
||||
let new_state = process_event_direct(&mut self.state_machine, event);
|
||||
|
||||
// Handle state entry/exit manually
|
||||
if new_state != self.state_machine.get_state() {
|
||||
self.on_state_exit(self.state_machine.get_state());
|
||||
self.state_machine.current_state = new_state;
|
||||
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;
|
||||
// Use process_event_direct method to avoid borrowing conflicts
|
||||
let new_state = process_event_direct(&mut self.state_machine, event);
|
||||
|
||||
// Handle state entry/exit manually
|
||||
if new_state != self.state_machine.get_state() {
|
||||
self.on_state_exit(self.state_machine.get_state());
|
||||
self.state_machine.current_state = new_state;
|
||||
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;
|
||||
// Use process_event_direct method to avoid borrowing conflicts
|
||||
let new_state = process_event_direct(&mut self.state_machine, event);
|
||||
|
||||
// Handle state entry/exit manually
|
||||
if new_state != self.state_machine.get_state() {
|
||||
self.on_state_exit(self.state_machine.get_state());
|
||||
self.state_machine.current_state = new_state;
|
||||
if new_state != old_state {
|
||||
self.on_state_exit(old_state);
|
||||
self.on_state_entry(new_state);
|
||||
}
|
||||
}
|
||||
|
|
@ -576,13 +568,11 @@ impl Component for Intel4003 {
|
|||
|
||||
// Process all events
|
||||
for event in events {
|
||||
// Use the process_event_direct method to avoid borrowing conflicts
|
||||
let old_state = self.state_machine.get_state();
|
||||
let new_state = process_event_direct(&mut self.state_machine, event);
|
||||
|
||||
// Handle state entry/exit manually
|
||||
if new_state != self.state_machine.get_state() {
|
||||
self.on_state_exit(self.state_machine.get_state());
|
||||
self.state_machine.current_state = new_state;
|
||||
if new_state != old_state {
|
||||
self.on_state_exit(old_state);
|
||||
self.on_state_entry(new_state);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
60
src/pin.rs
60
src/pin.rs
|
|
@ -113,6 +113,59 @@ impl Pin {
|
|||
self.recalculate_value();
|
||||
}
|
||||
|
||||
/// Set a driver on this pin without triggering propagation to connected pins.
|
||||
/// This is used during propagation to avoid deadlocks with bidirectional connections.
|
||||
fn set_driver_no_propagate(
|
||||
&mut self,
|
||||
driver_name: String,
|
||||
value: PinValue,
|
||||
strength: DriveStrength,
|
||||
) {
|
||||
if value == PinValue::HighZ && strength == DriveStrength::HighImpedance {
|
||||
self.drivers.remove(&driver_name);
|
||||
} else {
|
||||
self.drivers.insert(driver_name, (value, strength));
|
||||
}
|
||||
|
||||
self.last_update = Instant::now();
|
||||
// Recalculate value locally but do NOT propagate
|
||||
self.recalculate_value_no_propagate();
|
||||
}
|
||||
|
||||
/// Recalculate the settled value from current drivers without propagating.
|
||||
fn recalculate_value_no_propagate(&mut self) {
|
||||
if self.drivers.is_empty() {
|
||||
self.settled_value = PinValue::HighZ;
|
||||
return;
|
||||
}
|
||||
|
||||
let mut max_strength = DriveStrength::HighImpedance;
|
||||
for (_, strength) in self.drivers.values() {
|
||||
if *strength > max_strength {
|
||||
max_strength = *strength;
|
||||
}
|
||||
}
|
||||
if max_strength == DriveStrength::HighImpedance {
|
||||
self.settled_value = PinValue::HighZ;
|
||||
return;
|
||||
}
|
||||
|
||||
let strong_drivers: Vec<PinValue> = self
|
||||
.drivers
|
||||
.values()
|
||||
.filter(|(_, strength)| *strength == max_strength)
|
||||
.map(|(value, _)| *value)
|
||||
.collect();
|
||||
|
||||
if strong_drivers.iter().any(|v| *v == PinValue::Low) {
|
||||
self.settled_value = PinValue::Low;
|
||||
} else if strong_drivers.iter().any(|v| *v == PinValue::High) {
|
||||
self.settled_value = PinValue::High;
|
||||
} else {
|
||||
self.settled_value = PinValue::HighZ;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_driver(&mut self, driver_name: &str) {
|
||||
self.drivers.remove(driver_name);
|
||||
self.last_update = Instant::now();
|
||||
|
|
@ -190,9 +243,12 @@ impl Pin {
|
|||
|
||||
for connected_pin in &self.connected_pins {
|
||||
let mut pin = connected_pin.lock();
|
||||
// Copy all our drivers with their strengths
|
||||
// Copy all our drivers with their strengths using the no-propagate
|
||||
// variant to avoid deadlock with bidirectional connections.
|
||||
// When Pin A propagates to Pin B, Pin B must not propagate back
|
||||
// to Pin A (which is still locked by this thread).
|
||||
for (driver_name, (value, strength)) in &self.drivers {
|
||||
pin.set_driver_with_strength(Some(driver_name.clone()), *value, *strength);
|
||||
pin.set_driver_no_propagate(driver_name.clone(), *value, *strength);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue