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:
@@ -131,7 +131,7 @@ The explore screen shows the type in the signal detail header.
|
||||
|
||||
**Unified application (`apply_model`)**: connector layout and BSDL are two implementations of one `PinModel` interface (`src/system/pin_model.{hpp,cpp}`: `spec_for(pin_name)`, `layout()`, `source()`). `ConnectorModel` wraps `pin_role`/`pin_layout`; `BsdlPinModel` wraps a parsed `BsdlModel`, indexed by both port name and physical pad. A single `apply_model(Part*, const PinModel&)` materialises the layout pins missing from the netlist, then sets each pin's `spec` **only where the model speaks** (`spec.source != None`). Sources are ranked (`spec_source_rank` in `pin_spec.hpp`: UserOverride > Bsdl > ConnectorModel > Inferred > Imported) and apply_model refuses to overwrite a spec owned by a higher-rank source — so one source never clobbers a more authoritative one, which is also the basis for `check_source_conflicts`. `set-connector-type` and `attach-bsdl` both funnel through it (the latter via the thin `apply_bsdl` adapter); `verify` stays agnostic of where a spec came from. A future SPICE/Modelica source would be a third `PinModel`.
|
||||
|
||||
**`verify` (six passes)**: (1) typed pins — local mismatch between each pin's `expected_signal_type()` (derived from its `PinSpec`) and the actual signal type; (2) bridged nets — Power↔GndShield inconsistencies; (3) orphan summary `N orphan pin(s) at import (X imported NC, Y dropped singleton)` (filters out pins bridged via any `Connection::pin_map` — typically `FillIdentityNCs`-materialised); (4) **model-driven pin checks** (`check_pin_specs`): `DriveContention` (≥2 push-pull `Out` on a net), `UndrivenNet` (a **fully-modelled** net with input(s) but no driver — nets with any Unknown-direction pin are skipped, so un-modelled drivers don't cause false positives), `NcWired` (a no-connect pin on a multi-pin net); (5) **JTAG chain** (`check_jtag_chain`): collects TAP pins by `spec.function`, maps each to its net, emits `JtagTapIncomplete` / `JtagBusUnbridged` (TMS or TCK not common to all TAP devices) / `JtagChainBreak` (dangling TDO/TDI, chain fan-out, or not a single head→tail daisy chain); (6) **source conflicts** (`check_source_conflicts`): a pin the BSDL declares power/ground (a must-connect rail) that the netlist leaves unconnected — a rail floated in the schematic (`SourceConflict`; the reverse, a BSDL no-connect that *is* wired, is the `NcWired` check). The BFS-reached `(module, signal)` set for any signal is shown live in `explore`'s detail pane when a signal entry is selected.
|
||||
**`verify` (seven passes)**: (1) typed pins — local mismatch between each pin's `expected_signal_type()` (derived from its `PinSpec`) and the actual signal type; (2) bridged nets — Power↔GndShield inconsistencies; (3) orphan summary `N orphan pin(s) at import (X imported NC, Y dropped singleton)` (filters out pins bridged via any `Connection::pin_map` — typically `FillIdentityNCs`-materialised); (4) **model-driven pin checks** (`check_pin_specs`): `DriveContention` (≥2 push-pull `Out` on a net), `UndrivenNet` (a **fully-modelled** net with input(s) but no driver — nets with any Unknown-direction pin are skipped, so un-modelled drivers don't cause false positives), `NcWired` (a no-connect pin on a multi-pin net); (5) **JTAG chain** (`check_jtag_chain`): collects TAP pins by `spec.function`, maps each to its net, emits `JtagTapIncomplete` / `JtagBusUnbridged` (TMS or TCK not common to all TAP devices) / `JtagChainBreak` (dangling TDO/TDI, chain fan-out, or not a single head→tail daisy chain); (6) **source conflicts** (`check_source_conflicts`): a pin the BSDL declares power/ground (a must-connect rail) that the netlist leaves unconnected — a rail floated in the schematic (`SourceConflict`; the reverse, a BSDL no-connect that *is* wired, is the `NcWired` check); (7) **BSDL completeness** (`check_bsdl_completeness`): device power/ground ports (from the attached `.bsd`, re-parsed) with no matching pin on the netlist part — a rail the schematic symbol is missing (`BsdlPinMissing`, one aggregated finding per part). The BFS-reached `(module, signal)` set for any signal is shown live in `explore`'s detail pane when a signal entry is selected.
|
||||
|
||||
**`analyze` (post-processing pass)**: `analyze_system(System*) → AnalysisReport` (`src/system/analysis.{hpp,cpp}`) is a stateless read-only pass that detects structural signal groups and anomalies. Per-module (signals are module-scoped):
|
||||
|
||||
@@ -299,7 +299,7 @@ The analyze screen additionally surfaces two "verify-class" issues, computed the
|
||||
- **pin-role mismatch** — a pin whose `expected_signal_type()` (derived from its `PinSpec`, set by `set-connector-type` via `pin_role(connector_type, pin_name)`) disagrees with the actual signal type.
|
||||
- **net-mix** — a bridged net (BFS over `Connection::pin_map`, ≥ 2 members) where `net_type_consistent(net, &dominant)` returns false. Specifically, the net contains both `Power` and `GndShield` signals.
|
||||
|
||||
The `verify` command (not the analyze screen, yet) also emits the **model-driven `AnomalyKind`s** from `bsdl_check.{hpp,cpp}`: `DriveContention` / `UndrivenNet` / `NcWired` (`check_pin_specs`) and `JtagTapIncomplete` / `JtagChainBreak` / `JtagBusUnbridged` (`check_jtag_chain`); and `SourceConflict` (`check_source_conflicts` — a BSDL power/ground pin the netlist leaves unconnected). They consume the BSDL-populated `PinSpec` plus `compute_all_nets`, and are surfaced in three places: the `verify` command, the analyze screen's Issues pane (counted as `… N model`), and a `model:` health row on the dashboard. `check_pin_specs`/`check_jtag_chain` accept an optional precomputed net list, so verify, analyze and the dashboard each compute the nets once and reuse them across checks.
|
||||
The `verify` command (not the analyze screen, yet) also emits the **model-driven `AnomalyKind`s** from `bsdl_check.{hpp,cpp}`: `DriveContention` / `UndrivenNet` / `NcWired` (`check_pin_specs`) and `JtagTapIncomplete` / `JtagChainBreak` / `JtagBusUnbridged` (`check_jtag_chain`); `SourceConflict` (`check_source_conflicts` — a BSDL power/ground pin the netlist leaves unconnected); and `BsdlPinMissing` (`check_bsdl_completeness` — a device power/ground port absent from the netlist part, re-parsing the attached `.bsd`). They consume the BSDL-populated `PinSpec` plus `compute_all_nets`, and are surfaced in three places: the `verify` command, the analyze screen's Issues pane (counted as `… N model`), and a `model:` health row on the dashboard. `check_pin_specs`/`check_jtag_chain` accept an optional precomputed net list, so verify, analyze and the dashboard each compute the nets once and reuse them across checks.
|
||||
|
||||
### Component kind
|
||||
|
||||
|
||||
@@ -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 "?";
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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)" };
|
||||
|
||||
|
||||
@@ -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("[")
|
||||
|
||||
@@ -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)"));
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include "system/analysis.hpp"
|
||||
#include "system/bsdl_check.hpp"
|
||||
#include "system/bsdl_model.hpp"
|
||||
#include "system/parts.hpp"
|
||||
#include "system/pins.hpp"
|
||||
@@ -141,3 +143,27 @@ TEST_CASE("attached BSDL path persists and re-applies on restore") {
|
||||
std::remove(bsd);
|
||||
std::remove(snap);
|
||||
}
|
||||
|
||||
TEST_CASE("check_bsdl_completeness flags a device power pin absent from the part") {
|
||||
const char *bsd = "test_complete_demo.bsd";
|
||||
{ std::ofstream o(bsd); o << DEMO_BSDL; }
|
||||
|
||||
System sys;
|
||||
Module *m = sys.modules()->merge("CARD");
|
||||
Part *u = new Part("U1");
|
||||
// All pins EXCEPT VDD (a power port at ball C1) → its port is unmatched.
|
||||
for (const char *n : {"TCK", "TDI", "TDO", "TMS", "IO1", "GND"})
|
||||
u->add(new Pin(n));
|
||||
u->bsdl_path = bsd;
|
||||
m->add(u);
|
||||
|
||||
auto a = check_bsdl_completeness(&sys);
|
||||
REQUIRE(a.size() == 1);
|
||||
CHECK(a[0].kind == AnomalyKind::BsdlPinMissing);
|
||||
|
||||
// With VDD present (by ball or by name), no completeness issue.
|
||||
u->add(new Pin("VDD"));
|
||||
CHECK(check_bsdl_completeness(&sys).empty());
|
||||
|
||||
std::remove(bsd);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user