diff --git a/src/core/app/verify.cpp b/src/core/app/verify.cpp new file mode 100644 index 0000000..29ae7d4 --- /dev/null +++ b/src/core/app/verify.cpp @@ -0,0 +1,98 @@ +#include "core/app/verify.hpp" + +#include "core/domain/bsdl_check.hpp" +#include "core/domain/connect.hpp" +#include "core/domain/modules.hpp" +#include "core/domain/nets.hpp" +#include "core/domain/parts.hpp" +#include "core/domain/pins.hpp" +#include "core/domain/signals.hpp" +#include "core/domain/system.hpp" + +#include +#include +#include + +namespace app { + +VerifyReport verify(System *sys) +{ + VerifyReport r; + if (!sys) + return r; + + // Pass 1 — typed pins: expected (model) vs actual (net) signal type. + for (auto &mkv : *sys->modules()) { + Module *mod = mkv.second; + for (auto &pkv : *mod) { + Part *prt = pkv.second; + if (prt->connector_type.empty()) + continue; + for (auto &nkv : *prt) { + Pin *pin = nkv.second; + ++r.typed_pins; + SignalType expected = pin->expected_signal_type(); + if (expected == SignalType::Other) + continue; + Signal *s = pin->signal(); + SignalType actual = s ? s->type : SignalType::Other; + if (actual == expected) + continue; + RoleMismatch m; + m.module = mod->name; + m.part = prt->name; + m.pin = pin->name; + m.signal = s ? s->name : std::string("(NC)"); + m.expected = expected; + m.actual = actual; + r.role_mismatches.push_back(std::move(m)); + } + } + } + + // Pass 2 — bridged nets: flag Power/GndShield mixing. Compute the nets once + // here and reuse them for the model checks below. + std::vector nets = compute_all_nets(sys); + r.total_nets = (int)nets.size(); + for (const Net &n : nets) { + if (n.members.size() < 2) + continue; + ++r.bridged_nets; + SignalType dom; + if (net_type_consistent(n, dom)) + continue; + NetInconsistency ni; + for (const auto &mp : n.members) + ni.members.push_back({mp.first->name, mp.second->name, mp.second->type}); + r.net_inconsistencies.push_back(std::move(ni)); + } + + // Pass 3 — orphans: pins with no signal and not bridged via a connection. + std::unordered_set bridged_pins; + for (auto &ckv : *sys->connections()) + for (auto &wp : ckv.second->pin_map) { + if (wp.first) bridged_pins.insert(wp.first); + if (wp.second) bridged_pins.insert(wp.second); + } + for (auto &mkv : *sys->modules()) + for (auto &pkv : *mkv.second) + for (auto &nkv : *pkv.second) { + Pin *pin = nkv.second; + if (pin->signal() || bridged_pins.count(pin)) + continue; + if (pin->nc_origin == NcOrigin::ImportedUnconnected) + ++r.orphan_imported; + else if (pin->nc_origin == NcOrigin::DroppedSingleton) + ++r.orphan_dropped; + } + + // Passes 4-7 — model-driven checks (reuse the nets from pass 2). + r.pin_anomalies = check_pin_specs(sys, &nets); + r.jtag_anomalies = check_jtag_chain(sys, &nets); + r.conflict_anomalies = check_source_conflicts(sys); + r.completeness_anomalies = check_bsdl_completeness(sys); + + return r; +} + +} // namespace app diff --git a/src/core/app/verify.hpp b/src/core/app/verify.hpp new file mode 100644 index 0000000..007dcdf --- /dev/null +++ b/src/core/app/verify.hpp @@ -0,0 +1,61 @@ +#ifndef _APP_VERIFY_HPP_ +#define _APP_VERIFY_HPP_ + +#include "core/domain/analysis.hpp" // Anomaly +#include "core/domain/signal_type.hpp" // SignalType + +#include +#include + +class System; + +namespace app { + +// One typed-pin role mismatch: the connector/BSDL expectation disagrees with +// the actual net type. +struct RoleMismatch { + std::string module, part, pin; + std::string signal; ///< signal name, or "(NC)" + SignalType expected = SignalType::Other; + SignalType actual = SignalType::Other; +}; + +// One bridged net mixing Power and GndShield, with its members for display. +struct NetInconsistency { + struct Member { std::string module, signal; SignalType type; }; + std::vector members; +}; + +// The full result of `verify`: structured data only — no strings beyond the +// names, no formatting. Frontends (the verify command, the analyze screen, the +// dashboard) render it however they like. +struct VerifyReport { + int typed_pins = 0; ///< pins with a non-Other expectation considered + std::vector role_mismatches; + + int total_nets = 0; + int bridged_nets = 0; + std::vector net_inconsistencies; + + int orphan_imported = 0; + int orphan_dropped = 0; + + std::vector pin_anomalies; ///< check_pin_specs + std::vector jtag_anomalies; ///< check_jtag_chain + std::vector conflict_anomalies; ///< check_source_conflicts + std::vector completeness_anomalies; ///< check_bsdl_completeness + + int orphan_total() const { return orphan_imported + orphan_dropped; } + int model_total() const { + return (int)(pin_anomalies.size() + jtag_anomalies.size() + + conflict_anomalies.size() + completeness_anomalies.size()); + } +}; + +// Run every verify pass over the system and return the findings. Pure core — +// computes the nets once and feeds them to the net-based checks. +VerifyReport verify(System *sys); + +} // namespace app + +#endif // _APP_VERIFY_HPP_ diff --git a/src/frontends/tui/commands.cpp b/src/frontends/tui/commands.cpp index 0906856..18e2996 100644 --- a/src/frontends/tui/commands.cpp +++ b/src/frontends/tui/commands.cpp @@ -4,16 +4,16 @@ #include "core/domain/analysis.hpp" #include "core/domain/connect.hpp" #include "core/domain/modules.hpp" -#include "core/domain/nets.hpp" #include "core/domain/parts.hpp" #include "core/domain/persist.hpp" #include "core/domain/pin_role.hpp" #include "core/domain/pin_model.hpp" #include "core/domain/bsdl_model.hpp" -#include "core/domain/bsdl_check.hpp" #include "core/domain/pins.hpp" #include "core/domain/signals.hpp" #include "core/domain/system.hpp" + +#include "core/app/verify.hpp" #include "core/domain/transform.hpp" #include "core/domain/transform_vpx.hpp" @@ -22,7 +22,6 @@ #include #include #include -#include #include void Tui::RegisterCommands() { @@ -228,104 +227,43 @@ void Tui::RegisterCommands() { commands["verify"] = { {}, [this](auto &) { if (!sys) { Print("no system: run 'new' first."); return; } - int checked = 0; - int mismatches = 0; - for (auto &mkv : *sys->modules()) { - Module *mod = mkv.second; - for (auto &pkv : *mod) { - Part *prt = pkv.second; - if (prt->connector_type.empty()) continue; - for (auto &nkv : *prt) { - Pin *pin = nkv.second; - ++checked; - SignalType expected = pin->expected_signal_type(); - if (expected == SignalType::Other) continue; - Signal *s = pin->signal(); - SignalType actual = s ? s->type : SignalType::Other; - if (actual == expected) continue; - ++mismatches; - std::string sig_label = s ? s->name : std::string("(NC)"); - Print(" " + mod->name + "/" + prt->name + "/" + pin->name - + ": expected " + signal_type_name(expected) - + ", got " + signal_type_name(actual) - + " (signal: " + sig_label + ")"); - } - } - } - Print("verify: " + std::to_string(mismatches) + " local mismatch(es) over " - + std::to_string(checked) + " typed pin(s)."); + app::VerifyReport r = app::verify(sys.get()); - auto nets = compute_all_nets(sys.get()); - int bridged = 0, inconsistent = 0; - for (const auto &n : nets) { - if (n.members.size() < 2) continue; - ++bridged; - SignalType dom; - if (net_type_consistent(n, dom)) continue; - ++inconsistent; + for (const auto &m : r.role_mismatches) + Print(" " + m.module + "/" + m.part + "/" + m.pin + + ": expected " + signal_type_name(m.expected) + + ", got " + signal_type_name(m.actual) + + " (signal: " + m.signal + ")"); + Print("verify: " + std::to_string(r.role_mismatches.size()) + + " local mismatch(es) over " + std::to_string(r.typed_pins) + + " typed pin(s)."); + + for (const auto &ni : r.net_inconsistencies) { std::string line = " net mixes Power and GndShield:"; - for (const auto &mp : n.members) { - line += " " + mp.first->name + "/" + mp.second->name - + "(" + signal_type_name(mp.second->type) + ")"; - } + for (const auto &mem : ni.members) + line += " " + mem.module + "/" + mem.signal + + "(" + signal_type_name(mem.type) + ")"; Print(line); } - Print("verify: " + std::to_string(inconsistent) + " inconsistent net(s) over " - + std::to_string(bridged) + " bridged net(s) (" - + std::to_string(nets.size()) + " total)."); + Print("verify: " + std::to_string(r.net_inconsistencies.size()) + + " inconsistent net(s) over " + std::to_string(r.bridged_nets) + + " bridged net(s) (" + std::to_string(r.total_nets) + " total)."); - // Orphan pin report. A pin is "orphan" if it came out of import (or - // post-import drop) with no signal, and is still not bridged to a - // real signal via any Connection::pin_map. Use `nc-export` for the - // per-pin list. - std::unordered_set bridged_pins; - for (auto &ckv : *sys->connections()) - for (auto &wp : ckv.second->pin_map) { - if (wp.first) bridged_pins.insert(wp.first); - if (wp.second) bridged_pins.insert(wp.second); - } - int orph_imported = 0, orph_dropped = 0; - for (auto &mkv : *sys->modules()) - for (auto &pkv : *mkv.second) - for (auto &nkv : *pkv.second) { - Pin *pin = nkv.second; - if (pin->signal() || bridged_pins.count(pin)) continue; - if (pin->nc_origin == NcOrigin::ImportedUnconnected) ++orph_imported; - else if (pin->nc_origin == NcOrigin::DroppedSingleton) ++orph_dropped; - } - Print("verify: " + std::to_string(orph_imported + orph_dropped) - + " orphan pin(s) at import (" - + std::to_string(orph_imported) + " imported NC, " - + std::to_string(orph_dropped) + " dropped singleton)."); + Print("verify: " + std::to_string(r.orphan_total()) + + " orphan pin(s) at import (" + std::to_string(r.orphan_imported) + + " imported NC, " + std::to_string(r.orphan_dropped) + + " dropped singleton)."); - // Model-driven pin checks (drive contention / undriven net / NC-wired) - // from the PinSpec direction/function populated by connector/BSDL models. - auto pin_anoms = check_pin_specs(sys.get(), &nets); - for (const auto &a : pin_anoms) - Print(" [" + std::string(anomaly_kind_name(a.kind)) + "] " + a.message); - Print("verify: " + std::to_string(pin_anoms.size()) - + " model-driven pin anomaly(ies)."); - - // JTAG boundary-scan chain integrity (TAP pins → nets). - auto jtag_anoms = check_jtag_chain(sys.get(), &nets); - for (const auto &a : jtag_anoms) - Print(" [" + std::string(anomaly_kind_name(a.kind)) + "] " + a.message); - Print("verify: " + std::to_string(jtag_anoms.size()) - + " JTAG chain anomaly(ies)."); - - // Model-vs-netlist conflicts (e.g. a BSDL power pin left unconnected). - auto conflict_anoms = check_source_conflicts(sys.get()); - for (const auto &a : conflict_anoms) - Print(" [" + std::string(anomaly_kind_name(a.kind)) + "] " + a.message); - Print("verify: " + std::to_string(conflict_anoms.size()) - + " source-conflict(s)."); - - // BSDL completeness: device power/ground pins missing from the netlist. - auto missing_anoms = check_bsdl_completeness(sys.get()); - for (const auto &a : missing_anoms) - Print(" [" + std::string(anomaly_kind_name(a.kind)) + "] " + a.message); - Print("verify: " + std::to_string(missing_anoms.size()) - + " BSDL completeness issue(s)."); + // Each model-driven group: per-finding lines + a one-line summary. + auto render = [this](const std::vector &v, const char *tail) { + for (const auto &a : v) + Print(" [" + std::string(anomaly_kind_name(a.kind)) + "] " + a.message); + Print("verify: " + std::to_string(v.size()) + tail); + }; + render(r.pin_anomalies, " model-driven pin anomaly(ies)."); + render(r.jtag_anomalies, " JTAG chain anomaly(ies)."); + render(r.conflict_anomalies, " source-conflict(s)."); + render(r.completeness_anomalies, " BSDL completeness issue(s)."); }, true, "check pin roles, power/gnd net consistency, and (with BSDL) pin and JTAG checks" }; diff --git a/tests/test_verify.cpp b/tests/test_verify.cpp new file mode 100644 index 0000000..713574a --- /dev/null +++ b/tests/test_verify.cpp @@ -0,0 +1,80 @@ +#include + +#include "core/app/verify.hpp" +#include "core/domain/connect.hpp" +#include "core/domain/modules.hpp" +#include "core/domain/parts.hpp" +#include "core/domain/pins.hpp" +#include "core/domain/signals.hpp" +#include "core/domain/system.hpp" + +// app::verify is pure core: it takes a System* and returns a VerifyReport of +// structured findings, with no Print/dialog/FTXUI. These tests build small +// systems by hand and assert the report — no UI involved. + +TEST_CASE("verify on a null or empty system reports nothing") { + app::VerifyReport none = app::verify(nullptr); + CHECK(none.typed_pins == 0); + CHECK(none.total_nets == 0); + CHECK(none.role_mismatches.empty()); + + System sys; + app::VerifyReport r = app::verify(&sys); + CHECK(r.typed_pins == 0); + CHECK(r.total_nets == 0); + CHECK(r.bridged_nets == 0); + CHECK(r.net_inconsistencies.empty()); + CHECK(r.orphan_total() == 0); + CHECK(r.model_total() == 0); +} + +TEST_CASE("verify flags a bridged net that mixes Power and GndShield") { + // Two cards, one wired pin pair: A.NETA (Power) <-> B.NETB (GndShield). + System sys; + Module *a = sys.modules()->merge("A"); + Module *b = sys.modules()->merge("B"); + Part *ja = new Part("J1"); a->add(ja); + Part *jb = new Part("P1"); b->add(jb); + Pin *pa = new Pin("1"); ja->add(pa); + Pin *pb = new Pin("1"); jb->add(pb); + Signal *sa = a->signals->merge("NETA"); sa->type = SignalType::Power; + Signal *sb = b->signals->merge("NETB"); sb->type = SignalType::GndShield; + sa->add(pa); pa->connect(sa); + sb->add(pb); pb->connect(sb); + + Connection *c = new Connection("A.J1<->B.P1", a, ja, b, jb); + c->transform_name = "identity"; + c->pin_map.emplace_back(pa, pb); + sys.connections()->add(c); + + app::VerifyReport r = app::verify(&sys); + + CHECK(r.total_nets == 1); + CHECK(r.bridged_nets == 1); + REQUIRE(r.net_inconsistencies.size() == 1); + CHECK(r.net_inconsistencies[0].members.size() == 2); + // Both endpoints are present with their declared types. + bool seen_power = false, seen_gnd = false; + for (const auto &m : r.net_inconsistencies[0].members) { + if (m.type == SignalType::Power) seen_power = true; + if (m.type == SignalType::GndShield) seen_gnd = true; + } + CHECK(seen_power); + CHECK(seen_gnd); +} + +TEST_CASE("verify counts orphan pins by their import origin") { + System sys; + Module *m = sys.modules()->merge("M"); + Part *p = new Part("J1"); m->add(p); + Pin *imp = new Pin("1"); imp->nc_origin = NcOrigin::ImportedUnconnected; p->add(imp); + Pin *drp = new Pin("2"); drp->nc_origin = NcOrigin::DroppedSingleton; p->add(drp); + Pin *wired = new Pin("3"); p->add(wired); + Signal *s = m->signals->merge("NET"); s->add(wired); wired->connect(s); + + app::VerifyReport r = app::verify(&sys); + + CHECK(r.orphan_imported == 1); + CHECK(r.orphan_dropped == 1); + CHECK(r.orphan_total() == 2); +}