#include "signals.hpp" #include "parts.hpp" #include #include #include #include const char *signal_type_name(SignalType t) { switch (t) { case SignalType::Power: return "power"; case SignalType::GndShield: return "gnd"; case SignalType::Other: return "other"; } return "other"; } SignalType next_signal_type(SignalType t) { switch (t) { case SignalType::Power: return SignalType::GndShield; case SignalType::GndShield: return SignalType::Other; case SignalType::Other: return SignalType::Power; } return SignalType::Other; } bool signal_type_from_name(const std::string &s, SignalType &out) { std::string l = s; std::transform(l.begin(), l.end(), l.begin(), [](unsigned char c) { return std::tolower(c); }); if (l == "power" || l == "p") { out = SignalType::Power; return true; } if (l == "gnd" || l == "g" || l == "shield" || l == "ground") { out = SignalType::GndShield; return true; } if (l == "other" || l == "o" || l == "signal") { out = SignalType::Other; return true; } return false; } namespace { // Control/monitoring vocabulary: a name holding both a rail token and one of // these is a signal ABOUT a rail (feedback, enable, power-good, fault, …) — // not the rail itself. Matched against whole separator-delimited tokens // (uppercase, trailing digits stripped so EN1/PG0 still hit). Entries of // length ≥ 4 also match as a token *suffix*, catching fused (VSENSE, PWRGOOD) // and active-low (NFAULT) forms; short entries match exactly, so GREEN or // SENSOR never trip on EN / SENSE. const char *const kPowerControlTokens[] = { "SENSE", "SNS", "KELVIN", // remote / Kelvin sense "FB", "FDBK", "FEEDBACK", // regulator feedback "EN", "ENA", "ENABLE", "INH", "INHIBIT", // enable / inhibit "PG", "PGOOD", "PWRGD", "PWROK", // power-good "GOOD", "OK", "FAIL", "FAULT", "FLT", // status / fault "ALERT", "ALRT", "WARN", "MON", "IMON", "VMON", "PMON", // monitoring "DET", "DETECT", "PRSNT", "PRESENT", // presence detection "OC", "OCP", "OV", "OVP", "UV", "UVP", // protection trips "TRIP", "SHDN", "SHUTDOWN", "SEQ", "CTRL", "CTL", // sequencing / control "STAT", "STATUS", "ON", "OFF", "BTN", // on/off request "CS", "IRQ", }; bool is_power_control_token(std::string tok) { while (!tok.empty() && std::isdigit((unsigned char)tok.back())) tok.pop_back(); // EN1, PG0, FB2 … if (tok.empty()) return false; for (const char *lex : kPowerControlTokens) { size_t n = std::strlen(lex); if (tok == lex) return true; if (n >= 4 && tok.size() > n && tok.compare(tok.size() - n, n, lex) == 0) return true; // VSENSE, PWRGOOD, NFAULT … } return false; } // Split on every non-alphanumeric character. `u` is already uppercase. std::vector alnum_tokens(const std::string &u) { std::vector out; std::string cur; for (char c : u) { if (std::isalnum((unsigned char)c)) { cur += c; continue; } if (!cur.empty()) { out.push_back(std::move(cur)); cur.clear(); } } if (!cur.empty()) out.push_back(std::move(cur)); return out; } } // namespace // Heuristic. Names like GND, GNDA, SHIELD, CHASSIS → GndShield (name alone is // reliable there — left out of the control-token logic on purpose). Names // containing PWR/POWER/VCC/VDD/VEE/VSS, or starting with VS_/VBAT/+/− → rail // candidates; a rail candidate whose tokens include a control word (SENSE, // EN, PG, …) is downgraded to PowerAdjacent. Else Other. NameClassification classify_signal_name(const std::string &name) { NameClassification out; if (name.empty()) return out; std::string u = name; std::transform(u.begin(), u.end(), u.begin(), [](unsigned char c) { return std::toupper(c); }); auto contains = [&](const char *needle) { return u.find(needle) != std::string::npos; }; auto starts_with = [&](const char *needle) { return u.rfind(needle, 0) == 0; }; if (u == "GND" || u == "GROUND" || starts_with("GND_") || (starts_with("GND") && u.size() >= 4 && std::isalpha((unsigned char)u[3])) || contains("SHIELD") || contains("CHASSIS") || contains("EARTH")) { out.verdict = NameVerdict::GndShield; return out; } if (contains("PWR") || contains("POWER") || contains("VCC") || contains("VDD") || contains("VEE") || contains("VSS") || starts_with("VS_") || starts_with("VS1_") || starts_with("VS2_") || starts_with("VS3_") || starts_with("VS4_") || starts_with("VBAT") || starts_with("VBUS") || starts_with("+") || starts_with("-")) { for (const std::string &tok : alnum_tokens(u)) { if (is_power_control_token(tok)) { out.verdict = NameVerdict::PowerAdjacent; out.token = tok; return out; } } out.verdict = NameVerdict::Rail; return out; } return out; } SignalType infer_signal_type(const std::string &name) { switch (classify_signal_name(name).verdict) { case NameVerdict::Rail: return SignalType::Power; case NameVerdict::GndShield: return SignalType::GndShield; case NameVerdict::PowerAdjacent: case NameVerdict::Other: break; } return SignalType::Other; } Signal::Signal(std::string name) : SystemElementContainer(name), prnt(nullptr), type(SignalType::Other) {}; void Signal::add(Pin *pin) { string pname = pin->prnt->name + "." + pin->name; SystemElementContainer::add(pname, pin); } Signals::Signals(void): SystemElementContainer("signals") {} Signals::Signals(std::vector signals): SystemElementContainer("signals", signals) {} Signals::~Signals() { for (const auto& [key, value] : content) { delete value; } } int drop_singleton_signals(Signals *signals) { if (!signals) return 0; std::vector doomed; for (auto &kv : *signals) if (kv.second->size() == 1) doomed.push_back(kv.second); for (Signal *s : doomed) { // Detach the lone pin so it surfaces as `(NC)` in views. for (auto &pkv : *s) { pkv.second->connect(nullptr); pkv.second->nc_origin = NcOrigin::DroppedSingleton; } signals->remove(s->name); delete s; } return (int)doomed.size(); } void Signals::add(Signal *signal) { SystemElementContainer::add(signal); signal->prnt = this; }