use std::collections::BTreeMap;
use super::model::{PeripheralSignal, PinSpec, SiliconTarget, StrappingRequirement};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PinConflict {
UnsupportedFunction {
pin_name: String,
signal: PeripheralSignal,
chip_name: &'static str,
valid_alternatives: Vec<&'static PinSpec>,
},
PinCollision {
pin_name: String,
signals: Vec<String>,
},
ReservedPinUsed {
pin_name: String,
reason: &'static str,
},
UnknownPin {
pin_name: String,
chip_name: &'static str,
},
StrappingConflict {
pin_name: String,
requirement: StrappingRequirement,
net_name: String,
},
}
#[derive(Debug, Clone)]
pub struct AssignedPin {
pub pin_name: String,
pub net_name: String,
pub signal_name: String,
pub is_pulled_to_gnd: bool,
pub is_pulled_high: bool,
}
pub fn validate_pin_assignments(
target: &SiliconTarget,
assignments: &[AssignedPin],
) -> Vec<PinConflict> {
let mut conflicts = pin_collisions(assignments);
for a in assignments {
check_assignment(target, a, &mut conflicts);
}
conflicts
}
fn pin_collisions(assignments: &[AssignedPin]) -> Vec<PinConflict> {
let mut pin_to_signals: BTreeMap<&str, Vec<&str>> = BTreeMap::new();
for a in assignments {
pin_to_signals
.entry(&a.pin_name)
.or_default()
.push(&a.signal_name);
}
pin_to_signals
.into_iter()
.filter_map(|(pin_name, mut signals)| {
signals.sort();
signals.dedup();
(signals.len() > 1).then(|| PinConflict::PinCollision {
pin_name: pin_name.to_string(),
signals: signals.into_iter().map(str::to_string).collect(),
})
})
.collect()
}
fn check_assignment(target: &SiliconTarget, a: &AssignedPin, conflicts: &mut Vec<PinConflict>) {
let Some(pin_spec) = target.find_pin(&a.pin_name) else {
conflicts.push(PinConflict::UnknownPin {
pin_name: a.pin_name.clone(),
chip_name: target.name,
});
return;
};
if pin_spec.capabilities.is_reserved_flash {
conflicts.push(PinConflict::ReservedPinUsed {
pin_name: a.pin_name.clone(),
reason: "pin is permanently allocated to internal high-speed Flash/PSRAM bus",
});
return;
}
if let Some(req) = pin_spec.strapping.filter(|req| violates_strapping(req, a)) {
conflicts.push(PinConflict::StrappingConflict {
pin_name: a.pin_name.clone(),
requirement: req,
net_name: a.net_name.clone(),
});
}
conflicts.extend(unsupported_function(target, pin_spec, a));
}
fn unsupported_function(
target: &SiliconTarget,
pin_spec: &PinSpec,
a: &AssignedPin,
) -> Option<PinConflict> {
let sig =
PeripheralSignal::parse(&a.signal_name).or_else(|| PeripheralSignal::parse(&a.net_name))?;
if pin_spec.functions.is_empty() || pin_spec.supports_signal(&sig) {
return None;
}
Some(PinConflict::UnsupportedFunction {
pin_name: a.pin_name.clone(),
valid_alternatives: target.find_valid_pins_for_signal(&sig),
signal: sig,
chip_name: target.name,
})
}
fn violates_strapping(req: &StrappingRequirement, a: &AssignedPin) -> bool {
match req {
StrappingRequirement::MustBeHigh { .. } => {
let net_lower = a.net_name.to_ascii_lowercase();
let sig_lower = a.signal_name.to_ascii_lowercase();
a.is_pulled_to_gnd
|| net_lower.contains("gnd")
|| (net_lower.contains("led") && !net_lower.contains("vcc"))
|| (sig_lower.contains("led") && !sig_lower.contains("vcc"))
}
StrappingRequirement::MustBeLow { .. } => a.is_pulled_high,
}
}