#include #include "system/analysis.hpp" #include "system/modules.hpp" #include "system/parts.hpp" #include "system/pins.hpp" #include "system/signals.hpp" #include "system/system.hpp" #include #include namespace { // Add a signal of the given name to a module, plus a single pin on a // helper part so the signal is not stripped by `drop_singleton_signals` // when callers exercise that path. void add_signal(Module *mod, Part *prt, const std::string &sig_name) { Pin *pin = new Pin("p_" + sig_name); prt->add(pin); Signal *s = mod->signals->merge(sig_name); s->add(pin); pin->connect(s); } } // namespace TEST_CASE("analyze detects diff pairs and reports `_P` orphans only") { auto sys = std::make_unique(); Module *m = sys->modules()->merge("M"); Part *p = new Part("U1"); m->add(p); add_signal(m, p, "GETH_DA_P"); add_signal(m, p, "GETH_DA_N"); add_signal(m, p, "PCIE_RX_P"); // orphan: _P only → reported add_signal(m, p, "USB_DM"); // not a diff pair (no _P/_N) add_signal(m, p, "lower_p"); add_signal(m, p, "lower_n"); // lowercase still matches add_signal(m, p, "RESET_N"); // _N only → NOT reported (active-low) add_signal(m, p, "BOOTMODE_N"); // _N only → NOT reported AnalysisReport r = analyze_system(sys.get()); int dp = 0, orphans = 0; for (const auto &g : r.groups) if (g.kind == GroupKind::DiffPair) ++dp; for (const auto &a : r.anomalies) if (a.kind == AnomalyKind::DiffPairOrphan) ++orphans; CHECK(dp == 2); // GETH_DA + lower CHECK(orphans == 1); // only PCIE_RX_P } TEST_CASE("analyze detects buses with bracketed and underscore forms") { auto sys = std::make_unique(); Module *m = sys->modules()->merge("M"); Part *p = new Part("U1"); m->add(p); add_signal(m, p, "DATA[0]"); add_signal(m, p, "DATA[1]"); add_signal(m, p, "DATA[2]"); add_signal(m, p, "ADDR_0"); add_signal(m, p, "ADDR_1"); add_signal(m, p, "STANDALONE"); AnalysisReport r = analyze_system(sys.get()); int buses = 0; bool data_found = false, addr_found = false; for (const auto &g : r.groups) { if (g.kind != GroupKind::Bus) continue; ++buses; if (g.label.find("DATA") == 0) { data_found = true; CHECK(g.lo == 0); CHECK(g.hi == 2); CHECK(g.members.size() == 3); } else if (g.label.find("ADDR_") == 0) { addr_found = true; CHECK(g.lo == 0); CHECK(g.hi == 1); CHECK(g.members.size() == 2); } } CHECK(buses == 2); CHECK(data_found); CHECK(addr_found); } TEST_CASE("analyze flags bus gaps") { auto sys = std::make_unique(); Module *m = sys->modules()->merge("M"); Part *p = new Part("U1"); m->add(p); add_signal(m, p, "D[0]"); add_signal(m, p, "D[1]"); add_signal(m, p, "D[3]"); // gap at 2 add_signal(m, p, "D[5]"); // gap at 4 AnalysisReport r = analyze_system(sys.get()); int gaps = 0; for (const auto &a : r.anomalies) if (a.kind == AnomalyKind::BusGap) ++gaps; CHECK(gaps == 1); // one bus, one anomaly listing all missing indices } TEST_CASE("analyze ignores single-suffix \"buses\" and pure numbers") { auto sys = std::make_unique(); Module *m = sys->modules()->merge("M"); Part *p = new Part("U1"); m->add(p); add_signal(m, p, "CLK_100MHZ"); // not a bus (single signal) add_signal(m, p, "GND"); // no digit suffix AnalysisReport r = analyze_system(sys.get()); for (const auto &g : r.groups) CHECK(g.kind != GroupKind::Bus); } TEST_CASE("analyze rejects digits not preceded by `_` (false-positive guard)") { auto sys = std::make_unique(); Module *m = sys->modules()->merge("M"); Part *p = new Part("U1"); m->add(p); // GETH_01_VDD12, GETH_01_VDD13 look like a bus with stem "GETH_01_VDD" // but there is no underscore before the digits — must NOT be detected. add_signal(m, p, "GETH_01_VDD12"); add_signal(m, p, "GETH_01_VDD13"); add_signal(m, p, "GETH_01_VDD14"); AnalysisReport r = analyze_system(sys.get()); for (const auto &g : r.groups) CHECK(g.kind != GroupKind::Bus); } TEST_CASE("analyze skips internal `$xxx` Mentor names") { auto sys = std::make_unique(); Module *m = sys->modules()->merge("M"); Part *p = new Part("U1"); m->add(p); add_signal(m, p, "$N123"); add_signal(m, p, "$N124"); add_signal(m, p, "$N125"); add_signal(m, p, "$SIG_P"); add_signal(m, p, "$SIG_N"); AnalysisReport r = analyze_system(sys.get()); CHECK(r.groups.empty()); CHECK(r.anomalies.empty()); } TEST_CASE("analyze on empty / null system") { AnalysisReport r = analyze_system(nullptr); CHECK(r.groups.empty()); CHECK(r.anomalies.empty()); auto sys = std::make_unique(); r = analyze_system(sys.get()); CHECK(r.groups.empty()); CHECK(r.anomalies.empty()); } TEST_CASE("analyze scopes detection per module (no cross-module merge)") { auto sys = std::make_unique(); Module *m1 = sys->modules()->merge("M1"); Module *m2 = sys->modules()->merge("M2"); Part *p1 = new Part("U1"); m1->add(p1); Part *p2 = new Part("U1"); m2->add(p2); add_signal(m1, p1, "SIG_P"); add_signal(m2, p2, "SIG_N"); // matching half lives in another module AnalysisReport r = analyze_system(sys.get()); int dp = 0, orphans = 0; for (const auto &g : r.groups) if (g.kind == GroupKind::DiffPair) ++dp; for (const auto &a : r.anomalies) if (a.kind == AnomalyKind::DiffPairOrphan) ++orphans; CHECK(dp == 0); CHECK(orphans == 1); // only the `_P` side is reported (active-low rule) }