Power inference: classify rail+control names as power-adjacent, not suspect
Names holding both a rail token (VCC/VDD/PWR/...) and a control token (SENSE, EN, PG, FB, OK, FAULT, ...) are signals ABOUT a rail - feedback, enable, power-good - so their non-Power classification is confident. They used to land in the Suspect bucket, drowning the genuine ambiguities. - classify_signal_name(): 3-state name verdict (Rail / PowerAdjacent / GndShield / Other) with whole-token matching (trailing digits stripped, long lexemes also match as suffix: VSENSE, PWRGOOD, NFAULT). infer_signal_type() becomes a thin wrapper, so the dashboard suspect count and the export suspect column shrink automatically. - infer_signal_types(): PowerAdjacent -> Other + new `adjacent` stat, before the structural gate (a big-fanout sense net stays Other). - LoadResult.adjacent rendered by all three consumers (TUI command, script engine, wx log) - outputs kept in sync. - analyze Types tab: new [Pwr-adjacent] rows with the deciding token, deliberate sort order (Power, Suspect, Adjacent, Gnd), glossary entry. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,7 @@ LoadResult load_module(System *sys, const std::string &module_name,
|
||||
r.power = inf.power;
|
||||
r.gnd = inf.gnd;
|
||||
r.kept_other = inf.kept_other;
|
||||
r.adjacent = inf.adjacent;
|
||||
r.ok = true;
|
||||
} catch (const std::exception &e) {
|
||||
r.error = e.what();
|
||||
|
||||
@@ -23,6 +23,7 @@ struct LoadResult {
|
||||
int power = 0; ///< signals inferred Power (name + structure)
|
||||
int gnd = 0; ///< signals inferred GndShield (name)
|
||||
int kept_other = 0; ///< name said Power but evidence too weak → kept Other
|
||||
int adjacent = 0; ///< rail + control token (SENSE/EN/PG/…) → Other, not suspect
|
||||
};
|
||||
|
||||
// Import a module from a netlist/pinout file into `sys`, drop singleton signals,
|
||||
|
||||
@@ -189,7 +189,9 @@ private:
|
||||
+ " singleton/NC signal(s))" : ""));
|
||||
emit(" types: " + std::to_string(r.power) + " power, "
|
||||
+ std::to_string(r.gnd) + " gnd, " + std::to_string(r.kept_other)
|
||||
+ " suspect Power (name only — kept as Other)");
|
||||
+ " suspect Power (name only — kept as Other), "
|
||||
+ std::to_string(r.adjacent)
|
||||
+ " power-adjacent (control — kept as Other)");
|
||||
return true;
|
||||
}
|
||||
if (cmd == "connect" || cmd == "plug") {
|
||||
|
||||
@@ -276,13 +276,21 @@ SignalTypeInferenceStats infer_signal_types(System *sys) {
|
||||
Module *mod = mkv.second;
|
||||
for (auto &skv : *mod->signals) {
|
||||
Signal *s = skv.second;
|
||||
SignalType named = infer_signal_type(s->name);
|
||||
if (named == SignalType::GndShield) {
|
||||
NameClassification ncl = classify_signal_name(s->name);
|
||||
if (ncl.verdict == NameVerdict::GndShield) {
|
||||
s->type = SignalType::GndShield;
|
||||
++st.gnd;
|
||||
continue;
|
||||
}
|
||||
if (named == SignalType::Power) {
|
||||
if (ncl.verdict == NameVerdict::PowerAdjacent) {
|
||||
// A rail token next to a control token (SENSE, EN, PG, …):
|
||||
// a signal about a rail, confidently NOT the rail — never
|
||||
// suspect, whatever the fan-out.
|
||||
s->type = SignalType::Other;
|
||||
++st.adjacent;
|
||||
continue;
|
||||
}
|
||||
if (ncl.verdict == NameVerdict::Rail) {
|
||||
int fanout = (int)s->size();
|
||||
// Hard rule: a "power" net that touches fewer than three
|
||||
// pins cannot physically be a rail (a real rail goes to
|
||||
|
||||
@@ -62,6 +62,8 @@ struct SignalTypeInferenceStats {
|
||||
int power = 0; ///< Signals promoted to Power (name + structural).
|
||||
int gnd = 0; ///< Signals promoted to GndShield (name only).
|
||||
int kept_other = 0; ///< Name said Power but structural evidence too weak.
|
||||
int adjacent = 0; ///< Rail token + control token (SENSE/EN/PG/…) →
|
||||
///< confidently Other, never suspect.
|
||||
};
|
||||
|
||||
// Thresholds used by `infer_signal_types` (re-exposed so the analyze screen
|
||||
|
||||
@@ -5,8 +5,24 @@
|
||||
|
||||
enum class SignalType { Power, GndShield, Other };
|
||||
|
||||
// Name-level verdict, richer than SignalType. `PowerAdjacent` is the key
|
||||
// addition: a name holding BOTH a rail token (VCC/VDD/PWR/…) and a control
|
||||
// token (SENSE/EN/PG/FB/…) is a signal *about* a rail — measurement, enable,
|
||||
// power-good — not the rail itself. Its non-Power classification is therefore
|
||||
// confident, where a bare rail name without structural evidence stays suspect.
|
||||
enum class NameVerdict { Rail, GndShield, PowerAdjacent, Other };
|
||||
|
||||
struct NameClassification {
|
||||
NameVerdict verdict = NameVerdict::Other;
|
||||
std::string token; ///< PowerAdjacent only: the control token that decided it.
|
||||
};
|
||||
|
||||
NameClassification classify_signal_name(const std::string &signal_name);
|
||||
|
||||
const char *signal_type_name(SignalType t);
|
||||
bool signal_type_from_name(const std::string &s, SignalType &out);
|
||||
// Thin wrapper over classify_signal_name: Rail → Power, GndShield → GndShield,
|
||||
// PowerAdjacent/Other → Other.
|
||||
SignalType infer_signal_type(const std::string &signal_name);
|
||||
SignalType next_signal_type(SignalType t); // Power → GndShield → Other → Power
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
const char *signal_type_name(SignalType t) {
|
||||
@@ -36,11 +37,68 @@ bool signal_type_from_name(const std::string &s, SignalType &out) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Heuristic. Names like GND, GNDA, SHIELD, CHASSIS → GndShield.
|
||||
// Names containing PWR/POWER/VCC/VDD/VEE/VSS, or matching V±N or +N.NV
|
||||
// patterns, or starting with VS_/VS3_ → Power. Else Other.
|
||||
SignalType infer_signal_type(const std::string &name) {
|
||||
if (name.empty()) return SignalType::Other;
|
||||
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<std::string> alnum_tokens(const std::string &u) {
|
||||
std::vector<std::string> 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); });
|
||||
@@ -52,14 +110,14 @@ SignalType infer_signal_type(const std::string &name) {
|
||||
return u.rfind(needle, 0) == 0;
|
||||
};
|
||||
|
||||
if (u == "GND" || u == "GROUND") return SignalType::GndShield;
|
||||
if (starts_with("GND_")
|
||||
if (u == "GND" || u == "GROUND"
|
||||
|| starts_with("GND_")
|
||||
|| (starts_with("GND") && u.size() >= 4
|
||||
&& std::isalpha((unsigned char)u[3]))) {
|
||||
return SignalType::GndShield;
|
||||
&& std::isalpha((unsigned char)u[3]))
|
||||
|| contains("SHIELD") || contains("CHASSIS") || contains("EARTH")) {
|
||||
out.verdict = NameVerdict::GndShield;
|
||||
return out;
|
||||
}
|
||||
if (contains("SHIELD") || contains("CHASSIS") || contains("EARTH"))
|
||||
return SignalType::GndShield;
|
||||
|
||||
if (contains("PWR") || contains("POWER")
|
||||
|| contains("VCC") || contains("VDD") || contains("VEE") || contains("VSS")
|
||||
@@ -67,7 +125,25 @@ SignalType infer_signal_type(const std::string &name) {
|
||||
|| starts_with("VS3_") || starts_with("VS4_")
|
||||
|| starts_with("VBAT") || starts_with("VBUS")
|
||||
|| starts_with("+") || starts_with("-")) {
|
||||
return SignalType::Power;
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -152,7 +152,9 @@ void Tui::RegisterCommands() {
|
||||
Print(" types: " + std::to_string(r.power) + " power, "
|
||||
+ std::to_string(r.gnd) + " gnd, "
|
||||
+ std::to_string(r.kept_other)
|
||||
+ " suspect Power (name only — kept as Other)");
|
||||
+ " suspect Power (name only — kept as Other), "
|
||||
+ std::to_string(r.adjacent)
|
||||
+ " power-adjacent (control — kept as Other)");
|
||||
},
|
||||
/*prompt_for_missing=*/ true,
|
||||
"load a module from a netlist / pinout file (mentor, altium, ods)",
|
||||
|
||||
@@ -131,29 +131,39 @@ Component Tui::BuildAnalyzeScreen() {
|
||||
// ============================================================= Types
|
||||
// Power decisions (confirmed / refuted) and NC orphan breakdown.
|
||||
analyze_types.clear();
|
||||
int conf_pwr = 0, ref_pwr = 0, gnd = 0;
|
||||
struct Row { char kind; std::string mod, sig; int fanout; bool voltage; };
|
||||
int conf_pwr = 0, ref_pwr = 0, adj = 0, gnd = 0;
|
||||
struct Row { char kind; std::string mod, sig; int fanout; bool voltage;
|
||||
std::string token; };
|
||||
std::vector<Row> rows;
|
||||
for (auto &mkv : *sys->modules()) {
|
||||
Module *mod = mkv.second;
|
||||
for (auto &skv : *mod->signals) {
|
||||
Signal *s = skv.second;
|
||||
SignalType named = infer_signal_type(s->name);
|
||||
NameClassification ncl = classify_signal_name(s->name);
|
||||
char kind = 0;
|
||||
if (named == SignalType::GndShield && s->type == SignalType::GndShield) {
|
||||
std::string token;
|
||||
if (ncl.verdict == NameVerdict::GndShield && s->type == SignalType::GndShield) {
|
||||
kind = 'G'; ++gnd;
|
||||
} else if (named == SignalType::Power && s->type == SignalType::Power) {
|
||||
} else if (ncl.verdict == NameVerdict::Rail && s->type == SignalType::Power) {
|
||||
kind = 'P'; ++conf_pwr;
|
||||
} else if (named == SignalType::Power && s->type == SignalType::Other) {
|
||||
} else if (ncl.verdict == NameVerdict::Rail && s->type == SignalType::Other) {
|
||||
kind = 'R'; ++ref_pwr;
|
||||
} else if (ncl.verdict == NameVerdict::PowerAdjacent) {
|
||||
kind = 'A'; ++adj; token = ncl.token;
|
||||
} else continue;
|
||||
rows.push_back({kind, mod->name, s->name,
|
||||
(int)s->size(), has_voltage_pattern(s->name)});
|
||||
(int)s->size(), has_voltage_pattern(s->name),
|
||||
token});
|
||||
}
|
||||
}
|
||||
// Deliberate display order: confirmed rails, then the suspects (the
|
||||
// actionable residue), then the power-adjacent controls, gnd last.
|
||||
auto rank = [](char k) {
|
||||
return k == 'P' ? 0 : k == 'R' ? 1 : k == 'A' ? 2 : 3;
|
||||
};
|
||||
std::sort(rows.begin(), rows.end(),
|
||||
[](const Row &a, const Row &b) {
|
||||
if (a.kind != b.kind) return a.kind < b.kind;
|
||||
[&](const Row &a, const Row &b) {
|
||||
if (a.kind != b.kind) return rank(a.kind) < rank(b.kind);
|
||||
if (a.mod != b.mod) return a.mod < b.mod;
|
||||
return a.sig < b.sig;
|
||||
});
|
||||
@@ -181,6 +191,10 @@ Component Tui::BuildAnalyzeScreen() {
|
||||
else reason = "name only — fan-out "
|
||||
+ std::to_string(r.fanout)
|
||||
+ ", no voltage";
|
||||
} else if (r.kind == 'A') {
|
||||
tag = "[Pwr-adjacent] ";
|
||||
reason = "control token '" + r.token
|
||||
+ "' in name — kept as Other";
|
||||
} else {
|
||||
tag = "[Gnd] ";
|
||||
reason = "name match (fan-out " + std::to_string(r.fanout) + ")";
|
||||
@@ -200,7 +214,8 @@ Component Tui::BuildAnalyzeScreen() {
|
||||
|
||||
std::string types_header = "Types: " + std::to_string(conf_pwr)
|
||||
+ " Power, " + std::to_string(ref_pwr)
|
||||
+ " Suspect, " + std::to_string(gnd)
|
||||
+ " Suspect, " + std::to_string(adj)
|
||||
+ " Adjacent, " + std::to_string(gnd)
|
||||
+ " Gnd";
|
||||
|
||||
// Tab bar — horizontal headers, active one inverted.
|
||||
@@ -240,8 +255,13 @@ Component Tui::BuildAnalyzeScreen() {
|
||||
"Name suggests Power AND structure agrees: fan-out ≥ 4 pins, "
|
||||
"or a voltage pattern in the name (e.g. 3V3, 5V, 12V)."),
|
||||
term("Suspect Power",
|
||||
"Name suggests Power but the structural check failed — "
|
||||
"fan-out too low and no voltage in the name."),
|
||||
"Name suggests Power, no control token explains it, but the "
|
||||
"structural check failed — fan-out too low and no voltage "
|
||||
"in the name."),
|
||||
term("Pwr-adjacent",
|
||||
"Name holds a rail token AND a control token (SENSE, EN, PG, "
|
||||
"FB, …): a signal about a rail — measurement or control — "
|
||||
"not the rail itself. Confidently Other, never suspect."),
|
||||
term("Hard floor",
|
||||
"Fan-out below 3 pins forces Other regardless of the name. "
|
||||
"A real rail physically cannot live on 1-2 pads."),
|
||||
|
||||
@@ -352,9 +352,9 @@ void EssimFrame::OnLoad(wxCommandEvent &) {
|
||||
}
|
||||
Log(wxString::Format(
|
||||
"loaded '%s' from %s — %d part(s), %d signal(s)"
|
||||
" (dropped %d; types: %d power / %d gnd / %d suspect)",
|
||||
" (dropped %d; types: %d power / %d gnd / %d suspect / %d pwr-adjacent)",
|
||||
modname, path, r.parts, r.signals, r.dropped, r.power, r.gnd,
|
||||
r.kept_other));
|
||||
r.kept_other, r.adjacent));
|
||||
RebuildModelView();
|
||||
}
|
||||
|
||||
|
||||
@@ -235,7 +235,7 @@ TEST_CASE("infer_signal_types: Power requires name+structural agreement") {
|
||||
|
||||
Signal *p_3v3 = m->signals->merge("PWR_3V3"); fan_out(p_3v3, 3); // voltage + ≥ floor → Power
|
||||
Signal *vcc = m->signals->merge("VCC"); fan_out(vcc, 5); // fan-out ≥ 4 → Power
|
||||
Signal *pwr_ok = m->signals->merge("PWR_OK"); fan_out(pwr_ok, 1); // < 3 → hard floor → Other
|
||||
Signal *pwr_ok = m->signals->merge("PWR_OK"); fan_out(pwr_ok, 1); // control token → adjacent
|
||||
Signal *pwr_2 = m->signals->merge("PWR_2"); fan_out(pwr_2, 2); // < 3 → hard floor → Other
|
||||
Signal *gnd = m->signals->merge("GND"); fan_out(gnd, 1); // gnd: name alone
|
||||
Signal *clk = m->signals->merge("CLK_50MHZ"); fan_out(clk, 3); // not power-ish → Other
|
||||
@@ -243,7 +243,8 @@ TEST_CASE("infer_signal_types: Power requires name+structural agreement") {
|
||||
auto st = infer_signal_types(sys.get());
|
||||
CHECK(st.power == 2); // PWR_3V3, VCC
|
||||
CHECK(st.gnd == 1); // GND (name alone)
|
||||
CHECK(st.kept_other == 2); // PWR_OK, PWR_2 below the hard floor
|
||||
CHECK(st.kept_other == 1); // PWR_2 below the hard floor
|
||||
CHECK(st.adjacent == 1); // PWR_OK: power-good control, not suspect
|
||||
|
||||
CHECK(p_3v3->type == SignalType::Power);
|
||||
CHECK(vcc->type == SignalType::Power);
|
||||
@@ -253,6 +254,27 @@ TEST_CASE("infer_signal_types: Power requires name+structural agreement") {
|
||||
CHECK(clk->type == SignalType::Other);
|
||||
}
|
||||
|
||||
TEST_CASE("infer_signal_types: power-adjacent beats fan-out — a big sense net is still Other") {
|
||||
auto sys = std::make_unique<System>();
|
||||
Module *m = sys->modules()->merge("M");
|
||||
Part *p = new Part("U1"); m->add(p);
|
||||
|
||||
// VDD_CORE_SENSE with fan-out 5: structure alone would confirm Power,
|
||||
// but the control token settles it as a measurement net → Other,
|
||||
// counted adjacent (not suspect, not power).
|
||||
Signal *s = m->signals->merge("VDD_CORE_SENSE");
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
Pin *pin = new Pin("p" + std::to_string(i));
|
||||
p->add(pin); s->add(pin); pin->connect(s);
|
||||
}
|
||||
|
||||
auto st = infer_signal_types(sys.get());
|
||||
CHECK(st.power == 0);
|
||||
CHECK(st.kept_other == 0);
|
||||
CHECK(st.adjacent == 1);
|
||||
CHECK(s->type == SignalType::Other);
|
||||
}
|
||||
|
||||
TEST_CASE("infer_signal_types: fan-out hard floor overrides voltage in name") {
|
||||
auto sys = std::make_unique<System>();
|
||||
Module *m = sys->modules()->merge("M");
|
||||
|
||||
@@ -51,7 +51,42 @@ TEST_CASE("infer_signal_type: power family") {
|
||||
CHECK(infer_signal_type("VS3_5V0") == SignalType::Power);
|
||||
CHECK(infer_signal_type("+5V") == SignalType::Power);
|
||||
CHECK(infer_signal_type("-12V") == SignalType::Power);
|
||||
CHECK(infer_signal_type("VBAT_SENSE") == SignalType::Power);
|
||||
// Rail token + control token → power-adjacent, mapped to Other by the
|
||||
// wrapper (it is a sense line ABOUT VBAT, not the rail).
|
||||
CHECK(infer_signal_type("VBAT_SENSE") == SignalType::Other);
|
||||
}
|
||||
|
||||
TEST_CASE("classify_signal_name: rail vs power-adjacent control signals") {
|
||||
CHECK(classify_signal_name("VCC").verdict == NameVerdict::Rail);
|
||||
CHECK(classify_signal_name("VDD_3V3").verdict == NameVerdict::Rail);
|
||||
CHECK(classify_signal_name("+5V").verdict == NameVerdict::Rail);
|
||||
|
||||
NameClassification c = classify_signal_name("VDD_CORE_SENSE");
|
||||
CHECK(c.verdict == NameVerdict::PowerAdjacent);
|
||||
CHECK(c.token == "SENSE");
|
||||
|
||||
CHECK(classify_signal_name("VBAT_SENSE").verdict == NameVerdict::PowerAdjacent);
|
||||
CHECK(classify_signal_name("VCC_EN").verdict == NameVerdict::PowerAdjacent);
|
||||
CHECK(classify_signal_name("VCC_EN1").verdict == NameVerdict::PowerAdjacent); // trailing digit
|
||||
CHECK(classify_signal_name("VDD_FB").verdict == NameVerdict::PowerAdjacent);
|
||||
CHECK(classify_signal_name("PWR_GOOD").verdict == NameVerdict::PowerAdjacent);
|
||||
CHECK(classify_signal_name("PWR_OK").verdict == NameVerdict::PowerAdjacent);
|
||||
CHECK(classify_signal_name("VBUS_DET").verdict == NameVerdict::PowerAdjacent);
|
||||
CHECK(classify_signal_name("POWER_FAIL").verdict == NameVerdict::PowerAdjacent);
|
||||
CHECK(classify_signal_name("VDD_VSENSE").verdict == NameVerdict::PowerAdjacent); // fused suffix
|
||||
CHECK(classify_signal_name("PWR_NFAULT").verdict == NameVerdict::PowerAdjacent); // active-low
|
||||
|
||||
// Whole-token matching: SENSOR is not SENSE, GREEN is not EN —
|
||||
// these stay genuine rails.
|
||||
CHECK(classify_signal_name("VDD_SENSOR").verdict == NameVerdict::Rail);
|
||||
CHECK(classify_signal_name("VCC_GREEN").verdict == NameVerdict::Rail);
|
||||
|
||||
// No rail token at all → Other, even with a control word.
|
||||
CHECK(classify_signal_name("SPI_CS").verdict == NameVerdict::Other);
|
||||
CHECK(classify_signal_name("FAN_SENSE").verdict == NameVerdict::Other);
|
||||
|
||||
// GND family is deliberately left out of the control-token logic.
|
||||
CHECK(classify_signal_name("GND_RET").verdict == NameVerdict::GndShield);
|
||||
}
|
||||
|
||||
TEST_CASE("infer_signal_type: other (data signals)") {
|
||||
|
||||
Reference in New Issue
Block a user