P3: BSDL completeness check (missing device power/ground pins)

check_bsdl_completeness(System*): for each BSDL-attached part, re-parse the
.bsd and report the device power/ground ports with no matching pin on the
netlist part (matched by port name or physical pad) — a rail the schematic
symbol is missing. One aggregated BsdlPinMissing per part; restricted to
power/ground so unused I/O balls don't create noise. Surfaced as a 7th verify
pass and in the analyze/dashboard model counts. 76 cases / 327 assertions
green; the real 8-card system reports 0 (all FPGA rails present). This closes
out P3.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 16:21:02 +02:00
parent c9ac186a20
commit a914b9d7e8
9 changed files with 100 additions and 3 deletions

View File

@@ -30,6 +30,7 @@ const char *anomaly_kind_name(AnomalyKind k) {
case AnomalyKind::JtagChainBreak: return "jtag-chain-break";
case AnomalyKind::JtagBusUnbridged: return "jtag-bus-unbridged";
case AnomalyKind::SourceConflict: return "source-conflict";
case AnomalyKind::BsdlPinMissing: return "bsdl-pin-missing";
}
return "?";
}

View File

@@ -38,6 +38,7 @@ enum class AnomalyKind {
JtagChainBreak, ///< The TDO→TDI daisy chain is broken / not a single path.
JtagBusUnbridged, ///< TMS or TCK is not common to all TAP devices.
SourceConflict, ///< A model contradicts the netlist (e.g. BSDL power pin left NC).
BsdlPinMissing, ///< A BSDL power/ground port has no pin on the netlist part.
};
struct Anomaly {

View File

@@ -1,5 +1,6 @@
#include "bsdl_check.hpp"
#include "bsdl_model.hpp"
#include "connect.hpp"
#include "modules.hpp"
#include "nets.hpp"
@@ -325,3 +326,54 @@ std::vector<Anomaly> check_source_conflicts(System *sys)
return out;
}
std::vector<Anomaly> check_bsdl_completeness(System *sys)
{
std::vector<Anomaly> out;
if (!sys)
return out;
for (auto &mkv : *sys->modules())
for (auto &pkv : *mkv.second) {
Part *part = pkv.second;
if (part->bsdl_path.empty())
continue;
BsdlModel model = BsdlModel::from_file(part->bsdl_path);
if (!model.valid())
continue;
std::vector<std::string> missing; // absent power/ground ports
for (const auto &port : model.ports()) {
if (port.function != PinFunction::Power &&
port.function != PinFunction::Ground)
continue; // only must-have rails — unused I/O may be absent
bool present = (!port.name.empty() && part->exists(port.name)) ||
(!port.pad.empty() && part->exists(port.pad));
if (!present) {
std::string lbl = port.name;
if (!port.pad.empty())
lbl += "@" + port.pad;
missing.push_back(lbl);
}
}
if (!missing.empty()) {
std::string examples;
for (size_t i = 0; i < missing.size() && i < 5; ++i)
examples += (examples.empty() ? "" : ", ") + missing[i];
if (missing.size() > 5)
examples += ", …";
Anomaly a;
a.kind = AnomalyKind::BsdlPinMissing;
a.module = part->prnt;
a.message = mkv.first + "/" + part->name + ": "
+ std::to_string(missing.size())
+ " device power/ground pin(s) absent from the netlist ("
+ examples + ")";
out.push_back(std::move(a));
}
}
return out;
}

View File

@@ -36,4 +36,11 @@ std::vector<Anomaly> check_jtag_chain(System *sys, const std::vector<Net> *nets
// schematic. The reverse (BSDL no-connect wired in the netlist) is `NcWired`.
std::vector<Anomaly> check_source_conflicts(System *sys);
// Completeness: for every part with an attached BSDL, the device's power/ground
// ports that have no matching pin on the netlist part (matched by port name or
// physical pad) — i.e. a power/ground pin the schematic symbol is missing. One
// aggregated anomaly per part. Re-parses each attached `.bsd` (no model cache on
// Part yet), so it's bounded by the number of BSDL-attached parts.
std::vector<Anomaly> check_bsdl_completeness(System *sys);
#endif // _BSDL_CHECK_HPP_

View File

@@ -319,6 +319,13 @@ void Tui::RegisterCommands() {
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).");
}, true,
"check pin roles, bridged-net consistency, and model-driven pin specs (contention/undriven/NC)" };

View File

@@ -106,9 +106,11 @@ Component Tui::BuildAnalyzeScreen() {
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("[")

View File

@@ -154,7 +154,8 @@ Component Tui::BuildDashboardScreen() {
// 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_source_conflicts(sys.get()).size()
+ check_bsdl_completeness(sys.get()).size());
health_rows.push_back(health_line(n_model == 0,
"model: " + std::to_string(n_model) + " BSDL/JTAG anomaly(ies)"));