Extract verify into core (app::verify); thin the TUI command.
Move the 7-pass verify orchestration out of the TUI command lambda and
into core/app/verify.{hpp,cpp}: app::verify(System*) returns a structured
VerifyReport (role mismatches, net inconsistencies, orphan counts, the four
model-driven anomaly vectors) with no Print/dialog/FTXUI. The nets are
computed once and fed to the net-based checks.
The verify command is now a thin renderer over the report, byte-identical
output. Prune the now-dead nets.hpp / bsdl_check.hpp / <unordered_set>
includes from commands.cpp.
Add tests/test_verify.cpp: builds small systems by hand and asserts the
report (empty system, Power/GndShield bridged-net inconsistency, orphan
counts by import origin) — pure core, no UI.
This is the structuring extraction: the same VerifyReport can now back the
analyze screen's Issues pane and the dashboard health rows, removing the
triple duplication of passes 1-3.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
98
src/core/app/verify.cpp
Normal file
98
src/core/app/verify.cpp
Normal file
@@ -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 <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
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<Net> 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<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);
|
||||
}
|
||||
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
|
||||
61
src/core/app/verify.hpp
Normal file
61
src/core/app/verify.hpp
Normal file
@@ -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 <string>
|
||||
#include <vector>
|
||||
|
||||
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<Member> 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<RoleMismatch> role_mismatches;
|
||||
|
||||
int total_nets = 0;
|
||||
int bridged_nets = 0;
|
||||
std::vector<NetInconsistency> net_inconsistencies;
|
||||
|
||||
int orphan_imported = 0;
|
||||
int orphan_dropped = 0;
|
||||
|
||||
std::vector<Anomaly> pin_anomalies; ///< check_pin_specs
|
||||
std::vector<Anomaly> jtag_anomalies; ///< check_jtag_chain
|
||||
std::vector<Anomaly> conflict_anomalies; ///< check_source_conflicts
|
||||
std::vector<Anomaly> 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_
|
||||
@@ -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 <cstdlib>
|
||||
#include <exception>
|
||||
#include <fstream>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
|
||||
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<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;
|
||||
}
|
||||
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<Anomaly> &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" };
|
||||
|
||||
|
||||
80
tests/test_verify.cpp
Normal file
80
tests/test_verify.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
Reference in New Issue
Block a user