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:
Jan-Peter von Hunnius 2026-05-12 19:32:01 +02:00
parent 54144d10e9
commit 6a335a15d4
14 changed files with 4125 additions and 1062 deletions

View file

@ -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();

View file

@ -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

View file

@ -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 {

View file

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

View file

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

View file

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