From 25939998abb8f221cd8b925c97a0b55c7a113d1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Wed, 3 Jun 2026 20:04:31 +0200 Subject: [PATCH] De-dup verify passes: drive analyze screen + dashboard from app::verify. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The analyze Issues pane and the dashboard Health rows each recomputed the same verify passes inline (pin-role mismatches, Power/Gnd net-mix, NC orphan rollup, model-driven checks) — the third and second copies of what the verify command also did. Route both screens through app::verify(System*) instead, so the passes live in exactly one place. Enrich VerifyReport with a per-pin OrphanPin detail list (module/part/pin + dropped flag) so the dashboard can still nest its dropped-singleton breakdown under the NC health line without re-walking modules/parts/pins. Output is unchanged in both screens (same label formats, same numbers). Prune the now-dead includes (nets/bsdl_check/connect/parts/pins as applicable, ) from both screens. Extend tests/test_verify.cpp to cover the new orphans detail. Co-Authored-By: Claude Opus 4.8 --- src/core/app/verify.cpp | 11 ++- src/core/app/verify.hpp | 8 ++ src/frontends/tui/screen_analyze.cpp | 109 ++++++++----------------- src/frontends/tui/screen_dashboard.cpp | 73 ++++------------- tests/test_verify.cpp | 15 ++++ 5 files changed, 82 insertions(+), 134 deletions(-) diff --git a/src/core/app/verify.cpp b/src/core/app/verify.cpp index 29ae7d4..dfc0044 100644 --- a/src/core/app/verify.cpp +++ b/src/core/app/verify.cpp @@ -80,10 +80,17 @@ VerifyReport verify(System *sys) Pin *pin = nkv.second; if (pin->signal() || bridged_pins.count(pin)) continue; - if (pin->nc_origin == NcOrigin::ImportedUnconnected) + bool dropped; + if (pin->nc_origin == NcOrigin::ImportedUnconnected) { ++r.orphan_imported; - else if (pin->nc_origin == NcOrigin::DroppedSingleton) + dropped = false; + } else if (pin->nc_origin == NcOrigin::DroppedSingleton) { ++r.orphan_dropped; + dropped = true; + } else { + continue; + } + r.orphans.push_back({mkv.first, pkv.first, nkv.first, dropped}); } // Passes 4-7 — model-driven checks (reuse the nets from pass 2). diff --git a/src/core/app/verify.hpp b/src/core/app/verify.hpp index 007dcdf..38c905d 100644 --- a/src/core/app/verify.hpp +++ b/src/core/app/verify.hpp @@ -26,6 +26,13 @@ struct NetInconsistency { std::vector members; }; +// One orphan pin: no signal and not bridged via a connection. `dropped` is true +// for a dropped singleton (essim detached it), false for an import-time NC. +struct OrphanPin { + std::string module, part, pin; + bool dropped = false; +}; + // 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. @@ -39,6 +46,7 @@ struct VerifyReport { int orphan_imported = 0; int orphan_dropped = 0; + std::vector orphans; ///< per-pin detail (both origins) std::vector pin_anomalies; ///< check_pin_specs std::vector jtag_anomalies; ///< check_jtag_chain diff --git a/src/frontends/tui/screen_analyze.cpp b/src/frontends/tui/screen_analyze.cpp index 2f5f425..19c755c 100644 --- a/src/frontends/tui/screen_analyze.cpp +++ b/src/frontends/tui/screen_analyze.cpp @@ -1,13 +1,9 @@ #include "frontends/tui/tui.hpp" #include "frontends/tui/tui_helpers.hpp" +#include "core/app/verify.hpp" #include "core/domain/analysis.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" @@ -17,7 +13,6 @@ #include #include -#include using namespace ftxui; @@ -57,41 +52,23 @@ Component Tui::BuildAnalyzeScreen() { // connection), then structural anomalies from the analysis pass. analyze_issues.clear(); - int n_role_mismatches = 0, n_typed_pins = 0; - for (auto &mkv : *sys->modules()) - for (auto &pkv : *mkv.second) { - Part *prt = pkv.second; - if (prt->connector_type.empty()) continue; - for (auto &nkv : *prt) { - Pin *pin = nkv.second; - ++n_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; - ++n_role_mismatches; - std::string sig_label = s ? s->name : std::string("(NC)"); - analyze_issues.push_back( - "[pin-role] " + mkv.first + "/" + prt->name + "/" - + pin->name + ": expected " + signal_type_name(expected) - + ", got " + signal_type_name(actual) - + " (signal: " + sig_label + ")"); - } - } + // verify + structural anomalies. The verify passes (pin-role, net-mix, + // orphans, model checks) come from the shared core op; the structural + // anomalies (diff-pair/bus) come from analyze_system above. + app::VerifyReport vr = app::verify(sys.get()); - auto nets = compute_all_nets(sys.get()); - int n_bridged = 0, n_inconsistent = 0; - for (const auto &n : nets) { - if (n.members.size() < 2) continue; - ++n_bridged; - SignalType dom; - if (net_type_consistent(n, dom)) continue; - ++n_inconsistent; + for (const auto &m : vr.role_mismatches) + analyze_issues.push_back( + "[pin-role] " + m.module + "/" + m.part + "/" + m.pin + + ": expected " + signal_type_name(m.expected) + + ", got " + signal_type_name(m.actual) + + " (signal: " + m.signal + ")"); + + for (const auto &ni : vr.net_inconsistencies) { std::string line = "[net-mix] mixes Power and Gnd:"; - 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) + ")"; analyze_issues.push_back(std::move(line)); } @@ -100,28 +77,25 @@ Component Tui::BuildAnalyzeScreen() { + anomaly_kind_name(a.kind) + "] " + a.message); - // Model-driven checks (same as `verify`), reusing the nets above. - std::vector model_anoms; - { - auto a1 = check_pin_specs(sys.get(), &nets); - auto a2 = check_jtag_chain(sys.get(), &nets); - auto a3 = check_source_conflicts(sys.get()); - auto a4 = check_bsdl_completeness(sys.get()); - model_anoms.insert(model_anoms.end(), a1.begin(), a1.end()); - model_anoms.insert(model_anoms.end(), a2.begin(), a2.end()); - model_anoms.insert(model_anoms.end(), a3.begin(), a3.end()); - model_anoms.insert(model_anoms.end(), a4.begin(), a4.end()); - } - for (const auto &a : model_anoms) - analyze_issues.push_back(std::string("[") - + anomaly_kind_name(a.kind) + "] " - + a.message); - int n_model = (int)model_anoms.size(); + // Model-driven checks (pin / JTAG / source-conflict / completeness). + auto push_anoms = [this](const std::vector &v) { + for (const auto &a : v) + analyze_issues.push_back(std::string("[") + + anomaly_kind_name(a.kind) + "] " + + a.message); + }; + push_anoms(vr.pin_anomalies); + push_anoms(vr.jtag_anomalies); + push_anoms(vr.conflict_anomalies); + push_anoms(vr.completeness_anomalies); + int n_model = vr.model_total(); if (analyze_issues.empty()) analyze_issues.push_back("(no issue found)"); if (analyze_issue_idx >= (int)analyze_issues.size()) analyze_issue_idx = (int)analyze_issues.size() - 1; + int n_role_mismatches = (int)vr.role_mismatches.size(); + int n_inconsistent = (int)vr.net_inconsistencies.size(); std::string issues_header = "Issues (" + std::to_string(n_role_mismatches + n_inconsistent + (int)rep.anomalies.size() + n_model) @@ -215,26 +189,11 @@ Component Tui::BuildAnalyzeScreen() { std::string(tag) + r.mod + "/" + r.sig + " — " + reason); } - // NC orphan rollup — same filter as the verify pass. - 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; - } + // NC orphan rollup — from the shared verify report. analyze_types.push_back( - "[NC] orphan pin(s): " + std::to_string(orph_imported + orph_dropped) - + " (" + std::to_string(orph_imported) + " imported, " - + std::to_string(orph_dropped) + " dropped)"); + "[NC] orphan pin(s): " + std::to_string(vr.orphan_total()) + + " (" + std::to_string(vr.orphan_imported) + " imported, " + + std::to_string(vr.orphan_dropped) + " dropped)"); if (analyze_type_idx >= (int)analyze_types.size()) analyze_type_idx = (int)analyze_types.size() - 1; diff --git a/src/frontends/tui/screen_dashboard.cpp b/src/frontends/tui/screen_dashboard.cpp index 27f4dfd..0103747 100644 --- a/src/frontends/tui/screen_dashboard.cpp +++ b/src/frontends/tui/screen_dashboard.cpp @@ -1,13 +1,11 @@ #include "frontends/tui/tui.hpp" #include "frontends/tui/tui_helpers.hpp" +#include "core/app/verify.hpp" #include "core/domain/analysis.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" @@ -16,7 +14,6 @@ #include #include -#include #include using namespace ftxui; @@ -77,58 +74,23 @@ Component Tui::BuildDashboardScreen() { } int n_conn = (int)sys->connections()->size(); - // ---- verify-style health (recomputed; cheap on realistic sizes) ---- - int n_role_mismatches = 0, n_typed_pins = 0; - for (auto &mkv : *sys->modules()) - for (auto &pkv : *mkv.second) { - Part *prt = pkv.second; - if (prt->connector_type.empty()) continue; - for (auto &nkv : *prt) { - Pin *pin = nkv.second; - ++n_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) ++n_role_mismatches; - } - } + // ---- verify-style health (shared core op; cheap on realistic sizes) ---- + app::VerifyReport vr = app::verify(sys.get()); + int n_role_mismatches = (int)vr.role_mismatches.size(); + int n_typed_pins = vr.typed_pins; + int n_inconsistent = (int)vr.net_inconsistencies.size(); + int n_bridged = vr.bridged_nets; + int orph_imported = vr.orphan_imported; + int orph_dropped = vr.orphan_dropped; - auto nets = compute_all_nets(sys.get()); - int n_bridged = 0, n_inconsistent = 0; - for (const auto &n : nets) { - if (n.members.size() < 2) continue; - ++n_bridged; - SignalType dom; - if (!net_type_consistent(n, dom)) ++n_inconsistent; - } - - // ---- NC orphan summary (matches verify pass 3) ---- - 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; // Per-module list of dropped-singleton pins, for the detail rows below // the NC health line. The signal name is gone (the Signal object was // deleted by `drop_singleton_signals`), but the pin's full path is // enough to locate it in `explore`. std::map> dropped_by_module; - 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; - dropped_by_module[mkv.first].push_back( - pkv.first + "/" + nkv.first); - } - } + for (const auto &o : vr.orphans) + if (o.dropped) + dropped_by_module[o.module].push_back(o.part + "/" + o.pin); auto health_line = [](bool ok, const std::string &s) { return hbox({ @@ -144,7 +106,7 @@ Component Tui::BuildDashboardScreen() { + " typed pin(s)")); health_rows.push_back(health_line(n_inconsistent == 0, "nets: " + std::to_string(n_inconsistent) + " inconsistent over " - + std::to_string(n_bridged) + " bridged (" + std::to_string(nets.size()) + + std::to_string(n_bridged) + " bridged (" + std::to_string(vr.total_nets) + " total)")); int orph_total = orph_imported + orph_dropped; health_rows.push_back(health_line(orph_total == 0, @@ -172,12 +134,9 @@ Component Tui::BuildDashboardScreen() { } } - // Model-driven checks (BSDL pin specs, JTAG chain, source conflicts), - // reusing the nets computed above. - int n_model = (int)(check_pin_specs(sys.get(), &nets).size() - + check_jtag_chain(sys.get(), &nets).size() - + check_source_conflicts(sys.get()).size() - + check_bsdl_completeness(sys.get()).size()); + // Model-driven checks (BSDL pin specs, JTAG chain, source conflicts, + // completeness) — from the shared verify report. + int n_model = vr.model_total(); health_rows.push_back(health_line(n_model == 0, "model: " + std::to_string(n_model) + " BSDL/JTAG anomaly(ies)")); diff --git a/tests/test_verify.cpp b/tests/test_verify.cpp index 713574a..9edc309 100644 --- a/tests/test_verify.cpp +++ b/tests/test_verify.cpp @@ -77,4 +77,19 @@ TEST_CASE("verify counts orphan pins by their import origin") { CHECK(r.orphan_imported == 1); CHECK(r.orphan_dropped == 1); CHECK(r.orphan_total() == 2); + + // Per-pin detail carries the path and origin (the dashboard lists the + // dropped ones under the NC health row). + REQUIRE(r.orphans.size() == 2); + int n_dropped = 0; + bool dropped_path_ok = false; + for (const auto &o : r.orphans) { + if (o.dropped) { + ++n_dropped; + if (o.module == "M" && o.part == "J1" && o.pin == "2") + dropped_path_ok = true; + } + } + CHECK(n_dropped == 1); + CHECK(dropped_path_ok); }