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