Bug Fixes
This commit is contained in:
parent
ba1b0ddc22
commit
e69c549750
8 changed files with 1143 additions and 175 deletions
373
src/pin.rs
373
src/pin.rs
|
|
@ -1,50 +1,363 @@
|
|||
use std::sync::{Arc, Mutex, RwLock};
|
||||
use std::cmp::{Ordering, PartialOrd};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
// Pin value representation
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum PinValue {
|
||||
High,
|
||||
Low,
|
||||
HighZ, // High impedance (disconnected)
|
||||
High,
|
||||
HighZ, // Tri-state
|
||||
}
|
||||
|
||||
impl PinValue {
|
||||
pub fn to_str(&self) -> &'static str {
|
||||
match self {
|
||||
PinValue::Low => "Low",
|
||||
PinValue::High => "High",
|
||||
PinValue::HighZ => "HighZ",
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_char(&self) -> char {
|
||||
match self {
|
||||
PinValue::Low => '0',
|
||||
PinValue::High => '1',
|
||||
PinValue::HighZ => 'Z',
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_bool(value: bool) -> Self {
|
||||
if value {
|
||||
PinValue::High
|
||||
} else {
|
||||
PinValue::Low
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_bool(&self) -> Option<bool> {
|
||||
match self {
|
||||
PinValue::Low => Some(false),
|
||||
PinValue::High => Some(true),
|
||||
PinValue::HighZ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum DriveStrength {
|
||||
HighImpedance = 0,
|
||||
Weak = 1,
|
||||
Standard = 2,
|
||||
Strong = 3,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Pin {
|
||||
pub name: String,
|
||||
pub value: Arc<RwLock<PinValue>>,
|
||||
pub output_enable: Arc<Mutex<bool>>,
|
||||
pub connections: Arc<Mutex<Vec<Arc<Pin>>>>,
|
||||
name: String,
|
||||
drivers: HashMap<String, (PinValue, DriveStrength)>,
|
||||
settled_value: PinValue,
|
||||
last_update: Instant,
|
||||
settlement_time: Duration,
|
||||
connected_pins: Vec<Arc<Mutex<Pin>>>,
|
||||
}
|
||||
impl PartialOrd for DriveStrength {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some((*self as u8).cmp(&(*other as u8)))
|
||||
}
|
||||
}
|
||||
impl Pin {
|
||||
// Get the effective value considering all connected pins
|
||||
pub fn new(name: String) -> Self {
|
||||
Pin {
|
||||
name,
|
||||
drivers: HashMap::new(),
|
||||
settled_value: PinValue::HighZ,
|
||||
last_update: Instant::now(),
|
||||
settlement_time: Duration::from_nanos(10), // 10ns settlement time
|
||||
connected_pins: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn set_driver(&mut self, driver_name: Option<String>, value: PinValue) {
|
||||
let strength = DriveStrength::Standard;
|
||||
self.set_driver_with_strength(driver_name, value, strength);
|
||||
}
|
||||
|
||||
pub fn set_driver_with_strength(&mut self, driver_name: Option<String>, value: PinValue, strength: DriveStrength) {
|
||||
let driver_id = driver_name.unwrap_or_else(|| "anonymous".to_string());
|
||||
|
||||
if value == PinValue::HighZ && strength == DriveStrength::HighImpedance {
|
||||
self.drivers.remove(&driver_id);
|
||||
} else {
|
||||
self.drivers.insert(driver_id, (value, strength));
|
||||
}
|
||||
|
||||
self.last_update = Instant::now();
|
||||
self.recalculate_value();
|
||||
}
|
||||
|
||||
pub fn remove_driver(&mut self, driver_name: &str) {
|
||||
self.drivers.remove(driver_name);
|
||||
self.last_update = Instant::now();
|
||||
self.recalculate_value();
|
||||
}
|
||||
|
||||
pub fn read(&self) -> PinValue {
|
||||
let connections = self.connections.lock().unwrap();
|
||||
// If we're still within settlement time, return the previous value
|
||||
if self.last_update.elapsed() < self.settlement_time {
|
||||
return self.settled_value;
|
||||
}
|
||||
self.settled_value
|
||||
}
|
||||
|
||||
let mut found_high_pin = false;
|
||||
pub fn read_immediate(&self) -> PinValue {
|
||||
self.settled_value
|
||||
}
|
||||
|
||||
// Check if any connected pin is actively driving low
|
||||
for connected_pin in connections.iter() {
|
||||
let output_enable = connected_pin.output_enable.lock().unwrap();
|
||||
let value = connected_pin.value.read().unwrap();
|
||||
pub fn get_drivers(&self) -> &HashMap<String, (PinValue, DriveStrength)> {
|
||||
&self.drivers
|
||||
}
|
||||
|
||||
pub fn is_settled(&self) -> bool {
|
||||
self.last_update.elapsed() >= self.settlement_time
|
||||
}
|
||||
|
||||
if *output_enable && *value == PinValue::Low {
|
||||
return PinValue::Low;
|
||||
} else if *output_enable && *value == PinValue::High {
|
||||
found_high_pin = true;
|
||||
pub fn get_settlement_time(&self) -> Duration {
|
||||
self.settlement_time
|
||||
}
|
||||
|
||||
pub fn set_settlement_time(&mut self, time: Duration) {
|
||||
self.settlement_time = time;
|
||||
}
|
||||
|
||||
pub fn connect_to(&mut self, other_pin: Arc<Mutex<Pin>>) {
|
||||
if !self.connected_pins.iter().any(|p| Arc::ptr_eq(p, &other_pin)) {
|
||||
self.connected_pins.push(other_pin);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disconnect_from(&mut self, other_pin: &Arc<Mutex<Pin>>) {
|
||||
self.connected_pins.retain(|p| !Arc::ptr_eq(p, other_pin));
|
||||
}
|
||||
|
||||
pub fn get_connected_pins(&self) -> &Vec<Arc<Mutex<Pin>>> {
|
||||
&self.connected_pins
|
||||
}
|
||||
|
||||
pub fn propagate(&self) {
|
||||
for connected_pin in &self.connected_pins {
|
||||
if let Ok(mut pin) = connected_pin.lock() {
|
||||
// Copy our drivers to the connected pin (simulate electrical connection)
|
||||
let mut new_drivers = self.drivers.clone();
|
||||
|
||||
// Merge with existing drivers on the connected pin
|
||||
for (driver, value) in &pin.drivers {
|
||||
new_drivers.insert(driver.clone(), *value);
|
||||
}
|
||||
|
||||
pin.drivers = new_drivers;
|
||||
pin.last_update = Instant::now();
|
||||
pin.recalculate_value();
|
||||
}
|
||||
}
|
||||
|
||||
// If no one is driving, return HighZ
|
||||
if found_high_pin {
|
||||
PinValue::Low
|
||||
} else {
|
||||
PinValue::HighZ
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(&self, value: PinValue, output_enable: bool) {
|
||||
*self.value.write().unwrap() = value;
|
||||
*self.output_enable.lock().unwrap() = output_enable;
|
||||
fn recalculate_value(&mut self) {
|
||||
if self.drivers.is_empty() {
|
||||
self.settled_value = PinValue::HighZ;
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the strongest driver strength manually
|
||||
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;
|
||||
}
|
||||
|
||||
// Get all drivers with the strongest strength
|
||||
let strong_drivers: Vec<PinValue> = self.drivers.values()
|
||||
.filter(|(_, strength)| *strength == max_strength)
|
||||
.map(|(value, _)| *value)
|
||||
.collect();
|
||||
|
||||
// Resolve conflicts: Low dominates, then High, HighZ is ignored
|
||||
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;
|
||||
}
|
||||
|
||||
// Propagate to connected pins
|
||||
self.propagate();
|
||||
}
|
||||
pub fn clear_connections(&mut self) {
|
||||
self.connected_pins.clear();
|
||||
}
|
||||
|
||||
pub fn disconnect_from_pin(&mut self, other_pin: &Arc<Mutex<Pin>>) {
|
||||
self.connected_pins.retain(|p| !Arc::ptr_eq(p, other_pin));
|
||||
}
|
||||
|
||||
pub fn is_connected_to(&self, other_pin: &Arc<Mutex<Pin>>) -> bool {
|
||||
self.connected_pins.iter().any(|p| Arc::ptr_eq(p, other_pin))
|
||||
}
|
||||
|
||||
pub fn get_connection_count(&self) -> usize {
|
||||
self.connected_pins.len()
|
||||
}
|
||||
|
||||
pub fn to_string(&self) -> String {
|
||||
format!("{}: {}", self.name, self.settled_value.to_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Pin {
|
||||
fn default() -> Self {
|
||||
Pin::new("unnamed".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
// Helper implementations for easier testing and debugging
|
||||
impl std::fmt::Display for PinValue {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.to_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for Pin {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}: {}", self.name, self.settled_value)?;
|
||||
|
||||
if !self.drivers.is_empty() {
|
||||
write!(f, " [drivers: ")?;
|
||||
for (i, (driver, (value, strength))) in self.drivers.iter().enumerate() {
|
||||
if i > 0 { write!(f, ", ")?; }
|
||||
write!(f, "{}={}({})", driver, value.to_char(), *strength as u8)?;
|
||||
}
|
||||
write!(f, "]")?;
|
||||
}
|
||||
|
||||
if !self.is_settled() {
|
||||
write!(f, " (settling)")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
|
||||
#[test]
|
||||
fn test_pin_creation() {
|
||||
let pin = Pin::new("TEST".to_string());
|
||||
assert_eq!(pin.name(), "TEST");
|
||||
assert_eq!(pin.read(), PinValue::HighZ);
|
||||
assert!(pin.drivers.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pin_driving() {
|
||||
let mut pin = Pin::new("TEST".to_string());
|
||||
|
||||
// Drive high
|
||||
pin.set_driver(Some("driver1".to_string()), PinValue::High);
|
||||
assert_eq!(pin.read(), PinValue::High);
|
||||
|
||||
// Drive low - should override
|
||||
pin.set_driver(Some("driver2".to_string()), PinValue::Low);
|
||||
assert_eq!(pin.read(), PinValue::Low);
|
||||
|
||||
// Remove driver
|
||||
pin.remove_driver("driver2");
|
||||
assert_eq!(pin.read(), PinValue::High);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pin_strength() {
|
||||
let mut pin = Pin::new("TEST".to_string());
|
||||
|
||||
// Weak high
|
||||
pin.set_driver_with_strength(
|
||||
Some("weak".to_string()),
|
||||
PinValue::High,
|
||||
DriveStrength::Weak
|
||||
);
|
||||
assert_eq!(pin.read(), PinValue::High);
|
||||
|
||||
// Strong low should override weak high
|
||||
pin.set_driver_with_strength(
|
||||
Some("strong".to_string()),
|
||||
PinValue::Low,
|
||||
DriveStrength::Strong
|
||||
);
|
||||
assert_eq!(pin.read(), PinValue::Low);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pin_connection() {
|
||||
let pin1 = Arc::new(Mutex::new(Pin::new("PIN1".to_string())));
|
||||
let pin2 = Arc::new(Mutex::new(Pin::new("PIN2".to_string())));
|
||||
|
||||
// Connect pins
|
||||
{
|
||||
let mut p1 = pin1.lock().unwrap();
|
||||
p1.connect_to(pin2.clone());
|
||||
}
|
||||
|
||||
// Drive pin1
|
||||
{
|
||||
let mut p1 = pin1.lock().unwrap();
|
||||
p1.set_driver(Some("test".to_string()), PinValue::High);
|
||||
}
|
||||
|
||||
// Wait for propagation
|
||||
std::thread::sleep(Duration::from_millis(1));
|
||||
|
||||
// Check pin2
|
||||
let p2 = pin2.lock().unwrap();
|
||||
assert_eq!(p2.read(), PinValue::High);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pin_conflict_resolution() {
|
||||
let mut pin = Pin::new("TEST".to_string());
|
||||
|
||||
// Multiple drivers with same strength - Low should dominate
|
||||
pin.set_driver(Some("driver1".to_string()), PinValue::High);
|
||||
pin.set_driver(Some("driver2".to_string()), PinValue::Low);
|
||||
pin.set_driver(Some("driver3".to_string()), PinValue::High);
|
||||
|
||||
assert_eq!(pin.read(), PinValue::Low);
|
||||
|
||||
// Remove low driver - should settle to High
|
||||
pin.remove_driver("driver2");
|
||||
assert_eq!(pin.read(), PinValue::High);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_pin_tri_state() {
|
||||
let mut pin = Pin::new("TEST".to_string());
|
||||
|
||||
// Drive then tri-state
|
||||
pin.set_driver(Some("driver".to_string()), PinValue::High);
|
||||
assert_eq!(pin.read(), PinValue::High);
|
||||
|
||||
pin.set_driver(Some("driver".to_string()), PinValue::HighZ);
|
||||
assert_eq!(pin.read(), PinValue::HighZ);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue