De-dup verify passes: drive analyze screen + dashboard from app::verify.
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, <unordered_set>) from both screens. Extend tests/test_verify.cpp to cover the new orphans detail. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -80,10 +80,17 @@ VerifyReport verify(System *sys)
|
|||||||
Pin *pin = nkv.second;
|
Pin *pin = nkv.second;
|
||||||
if (pin->signal() || bridged_pins.count(pin))
|
if (pin->signal() || bridged_pins.count(pin))
|
||||||
continue;
|
continue;
|
||||||
if (pin->nc_origin == NcOrigin::ImportedUnconnected)
|
bool dropped;
|
||||||
|
if (pin->nc_origin == NcOrigin::ImportedUnconnected) {
|
||||||
++r.orphan_imported;
|
++r.orphan_imported;
|
||||||
else if (pin->nc_origin == NcOrigin::DroppedSingleton)
|
dropped = false;
|
||||||
|
} else if (pin->nc_origin == NcOrigin::DroppedSingleton) {
|
||||||
++r.orphan_dropped;
|
++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).
|
// Passes 4-7 — model-driven checks (reuse the nets from pass 2).
|
||||||
|
|||||||
@@ -26,6 +26,13 @@ struct NetInconsistency {
|
|||||||
std::vector<Member> members;
|
std::vector<Member> 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
|
// The full result of `verify`: structured data only — no strings beyond the
|
||||||
// names, no formatting. Frontends (the verify command, the analyze screen, the
|
// names, no formatting. Frontends (the verify command, the analyze screen, the
|
||||||
// dashboard) render it however they like.
|
// dashboard) render it however they like.
|
||||||
@@ -39,6 +46,7 @@ struct VerifyReport {
|
|||||||
|
|
||||||
int orphan_imported = 0;
|
int orphan_imported = 0;
|
||||||
int orphan_dropped = 0;
|
int orphan_dropped = 0;
|
||||||
|
std::vector<OrphanPin> orphans; ///< per-pin detail (both origins)
|
||||||
|
|
||||||
std::vector<Anomaly> pin_anomalies; ///< check_pin_specs
|
std::vector<Anomaly> pin_anomalies; ///< check_pin_specs
|
||||||
std::vector<Anomaly> jtag_anomalies; ///< check_jtag_chain
|
std::vector<Anomaly> jtag_anomalies; ///< check_jtag_chain
|
||||||
|
|||||||
@@ -1,13 +1,9 @@
|
|||||||
#include "frontends/tui/tui.hpp"
|
#include "frontends/tui/tui.hpp"
|
||||||
#include "frontends/tui/tui_helpers.hpp"
|
#include "frontends/tui/tui_helpers.hpp"
|
||||||
|
|
||||||
|
#include "core/app/verify.hpp"
|
||||||
#include "core/domain/analysis.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/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/signals.hpp"
|
||||||
#include "core/domain/system.hpp"
|
#include "core/domain/system.hpp"
|
||||||
|
|
||||||
@@ -17,7 +13,6 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <unordered_set>
|
|
||||||
|
|
||||||
using namespace ftxui;
|
using namespace ftxui;
|
||||||
|
|
||||||
@@ -57,41 +52,23 @@ Component Tui::BuildAnalyzeScreen() {
|
|||||||
// connection), then structural anomalies from the analysis pass.
|
// connection), then structural anomalies from the analysis pass.
|
||||||
analyze_issues.clear();
|
analyze_issues.clear();
|
||||||
|
|
||||||
int n_role_mismatches = 0, n_typed_pins = 0;
|
// verify + structural anomalies. The verify passes (pin-role, net-mix,
|
||||||
for (auto &mkv : *sys->modules())
|
// orphans, model checks) come from the shared core op; the structural
|
||||||
for (auto &pkv : *mkv.second) {
|
// anomalies (diff-pair/bus) come from analyze_system above.
|
||||||
Part *prt = pkv.second;
|
app::VerifyReport vr = app::verify(sys.get());
|
||||||
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 + ")");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto nets = compute_all_nets(sys.get());
|
for (const auto &m : vr.role_mismatches)
|
||||||
int n_bridged = 0, n_inconsistent = 0;
|
analyze_issues.push_back(
|
||||||
for (const auto &n : nets) {
|
"[pin-role] " + m.module + "/" + m.part + "/" + m.pin
|
||||||
if (n.members.size() < 2) continue;
|
+ ": expected " + signal_type_name(m.expected)
|
||||||
++n_bridged;
|
+ ", got " + signal_type_name(m.actual)
|
||||||
SignalType dom;
|
+ " (signal: " + m.signal + ")");
|
||||||
if (net_type_consistent(n, dom)) continue;
|
|
||||||
++n_inconsistent;
|
for (const auto &ni : vr.net_inconsistencies) {
|
||||||
std::string line = "[net-mix] mixes Power and Gnd:";
|
std::string line = "[net-mix] mixes Power and Gnd:";
|
||||||
for (const auto &mp : n.members)
|
for (const auto &mem : ni.members)
|
||||||
line += " " + mp.first->name + "/" + mp.second->name
|
line += " " + mem.module + "/" + mem.signal
|
||||||
+ "(" + signal_type_name(mp.second->type) + ")";
|
+ "(" + signal_type_name(mem.type) + ")";
|
||||||
analyze_issues.push_back(std::move(line));
|
analyze_issues.push_back(std::move(line));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,28 +77,25 @@ Component Tui::BuildAnalyzeScreen() {
|
|||||||
+ anomaly_kind_name(a.kind) + "] "
|
+ anomaly_kind_name(a.kind) + "] "
|
||||||
+ a.message);
|
+ a.message);
|
||||||
|
|
||||||
// Model-driven checks (same as `verify`), reusing the nets above.
|
// Model-driven checks (pin / JTAG / source-conflict / completeness).
|
||||||
std::vector<Anomaly> model_anoms;
|
auto push_anoms = [this](const std::vector<Anomaly> &v) {
|
||||||
{
|
for (const auto &a : v)
|
||||||
auto a1 = check_pin_specs(sys.get(), &nets);
|
analyze_issues.push_back(std::string("[")
|
||||||
auto a2 = check_jtag_chain(sys.get(), &nets);
|
+ anomaly_kind_name(a.kind) + "] "
|
||||||
auto a3 = check_source_conflicts(sys.get());
|
+ a.message);
|
||||||
auto a4 = check_bsdl_completeness(sys.get());
|
};
|
||||||
model_anoms.insert(model_anoms.end(), a1.begin(), a1.end());
|
push_anoms(vr.pin_anomalies);
|
||||||
model_anoms.insert(model_anoms.end(), a2.begin(), a2.end());
|
push_anoms(vr.jtag_anomalies);
|
||||||
model_anoms.insert(model_anoms.end(), a3.begin(), a3.end());
|
push_anoms(vr.conflict_anomalies);
|
||||||
model_anoms.insert(model_anoms.end(), a4.begin(), a4.end());
|
push_anoms(vr.completeness_anomalies);
|
||||||
}
|
int n_model = vr.model_total();
|
||||||
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();
|
|
||||||
|
|
||||||
if (analyze_issues.empty()) analyze_issues.push_back("(no issue found)");
|
if (analyze_issues.empty()) analyze_issues.push_back("(no issue found)");
|
||||||
if (analyze_issue_idx >= (int)analyze_issues.size())
|
if (analyze_issue_idx >= (int)analyze_issues.size())
|
||||||
analyze_issue_idx = (int)analyze_issues.size() - 1;
|
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::string issues_header = "Issues ("
|
||||||
+ std::to_string(n_role_mismatches + n_inconsistent
|
+ std::to_string(n_role_mismatches + n_inconsistent
|
||||||
+ (int)rep.anomalies.size() + n_model)
|
+ (int)rep.anomalies.size() + n_model)
|
||||||
@@ -215,26 +189,11 @@ Component Tui::BuildAnalyzeScreen() {
|
|||||||
std::string(tag) + r.mod + "/" + r.sig + " — " + reason);
|
std::string(tag) + r.mod + "/" + r.sig + " — " + reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
// NC orphan rollup — same filter as the verify pass.
|
// NC orphan rollup — from the shared verify report.
|
||||||
std::unordered_set<Pin *> 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;
|
|
||||||
}
|
|
||||||
analyze_types.push_back(
|
analyze_types.push_back(
|
||||||
"[NC] orphan pin(s): " + std::to_string(orph_imported + orph_dropped)
|
"[NC] orphan pin(s): " + std::to_string(vr.orphan_total())
|
||||||
+ " (" + std::to_string(orph_imported) + " imported, "
|
+ " (" + std::to_string(vr.orphan_imported) + " imported, "
|
||||||
+ std::to_string(orph_dropped) + " dropped)");
|
+ std::to_string(vr.orphan_dropped) + " dropped)");
|
||||||
|
|
||||||
if (analyze_type_idx >= (int)analyze_types.size())
|
if (analyze_type_idx >= (int)analyze_types.size())
|
||||||
analyze_type_idx = (int)analyze_types.size() - 1;
|
analyze_type_idx = (int)analyze_types.size() - 1;
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
#include "frontends/tui/tui.hpp"
|
#include "frontends/tui/tui.hpp"
|
||||||
#include "frontends/tui/tui_helpers.hpp"
|
#include "frontends/tui/tui_helpers.hpp"
|
||||||
|
|
||||||
|
#include "core/app/verify.hpp"
|
||||||
#include "core/domain/analysis.hpp"
|
#include "core/domain/analysis.hpp"
|
||||||
#include "core/domain/bsdl_check.hpp"
|
|
||||||
#include "core/domain/connect.hpp"
|
#include "core/domain/connect.hpp"
|
||||||
#include "core/domain/modules.hpp"
|
#include "core/domain/modules.hpp"
|
||||||
#include "core/domain/nets.hpp"
|
|
||||||
#include "core/domain/parts.hpp"
|
#include "core/domain/parts.hpp"
|
||||||
#include "core/domain/pins.hpp"
|
|
||||||
#include "core/domain/signals.hpp"
|
#include "core/domain/signals.hpp"
|
||||||
#include "core/domain/system.hpp"
|
#include "core/domain/system.hpp"
|
||||||
|
|
||||||
@@ -16,7 +14,6 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <unordered_set>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
using namespace ftxui;
|
using namespace ftxui;
|
||||||
@@ -77,58 +74,23 @@ Component Tui::BuildDashboardScreen() {
|
|||||||
}
|
}
|
||||||
int n_conn = (int)sys->connections()->size();
|
int n_conn = (int)sys->connections()->size();
|
||||||
|
|
||||||
// ---- verify-style health (recomputed; cheap on realistic sizes) ----
|
// ---- verify-style health (shared core op; cheap on realistic sizes) ----
|
||||||
int n_role_mismatches = 0, n_typed_pins = 0;
|
app::VerifyReport vr = app::verify(sys.get());
|
||||||
for (auto &mkv : *sys->modules())
|
int n_role_mismatches = (int)vr.role_mismatches.size();
|
||||||
for (auto &pkv : *mkv.second) {
|
int n_typed_pins = vr.typed_pins;
|
||||||
Part *prt = pkv.second;
|
int n_inconsistent = (int)vr.net_inconsistencies.size();
|
||||||
if (prt->connector_type.empty()) continue;
|
int n_bridged = vr.bridged_nets;
|
||||||
for (auto &nkv : *prt) {
|
int orph_imported = vr.orphan_imported;
|
||||||
Pin *pin = nkv.second;
|
int orph_dropped = vr.orphan_dropped;
|
||||||
++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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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<Pin *> 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
|
// 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
|
// 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
|
// deleted by `drop_singleton_signals`), but the pin's full path is
|
||||||
// enough to locate it in `explore`.
|
// enough to locate it in `explore`.
|
||||||
std::map<std::string, std::vector<std::string>> dropped_by_module;
|
std::map<std::string, std::vector<std::string>> dropped_by_module;
|
||||||
for (auto &mkv : *sys->modules())
|
for (const auto &o : vr.orphans)
|
||||||
for (auto &pkv : *mkv.second)
|
if (o.dropped)
|
||||||
for (auto &nkv : *pkv.second) {
|
dropped_by_module[o.module].push_back(o.part + "/" + o.pin);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto health_line = [](bool ok, const std::string &s) {
|
auto health_line = [](bool ok, const std::string &s) {
|
||||||
return hbox({
|
return hbox({
|
||||||
@@ -144,7 +106,7 @@ Component Tui::BuildDashboardScreen() {
|
|||||||
+ " typed pin(s)"));
|
+ " typed pin(s)"));
|
||||||
health_rows.push_back(health_line(n_inconsistent == 0,
|
health_rows.push_back(health_line(n_inconsistent == 0,
|
||||||
"nets: " + std::to_string(n_inconsistent) + " inconsistent over "
|
"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)"));
|
+ " total)"));
|
||||||
int orph_total = orph_imported + orph_dropped;
|
int orph_total = orph_imported + orph_dropped;
|
||||||
health_rows.push_back(health_line(orph_total == 0,
|
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),
|
// Model-driven checks (BSDL pin specs, JTAG chain, source conflicts,
|
||||||
// reusing the nets computed above.
|
// completeness) — from the shared verify report.
|
||||||
int n_model = (int)(check_pin_specs(sys.get(), &nets).size()
|
int n_model = vr.model_total();
|
||||||
+ check_jtag_chain(sys.get(), &nets).size()
|
|
||||||
+ check_source_conflicts(sys.get()).size()
|
|
||||||
+ check_bsdl_completeness(sys.get()).size());
|
|
||||||
health_rows.push_back(health_line(n_model == 0,
|
health_rows.push_back(health_line(n_model == 0,
|
||||||
"model: " + std::to_string(n_model) + " BSDL/JTAG anomaly(ies)"));
|
"model: " + std::to_string(n_model) + " BSDL/JTAG anomaly(ies)"));
|
||||||
|
|
||||||
|
|||||||
@@ -77,4 +77,19 @@ TEST_CASE("verify counts orphan pins by their import origin") {
|
|||||||
CHECK(r.orphan_imported == 1);
|
CHECK(r.orphan_imported == 1);
|
||||||
CHECK(r.orphan_dropped == 1);
|
CHECK(r.orphan_dropped == 1);
|
||||||
CHECK(r.orphan_total() == 2);
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user