diff --git a/DESIGN.md b/DESIGN.md index 60064f4..290ed26 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -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 diff --git a/src/system/analysis.cpp b/src/system/analysis.cpp index b7b5a95..144069c 100644 --- a/src/system/analysis.cpp +++ b/src/system/analysis.cpp @@ -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 "?"; } diff --git a/src/system/analysis.hpp b/src/system/analysis.hpp index d4b245a..89d5b19 100644 --- a/src/system/analysis.hpp +++ b/src/system/analysis.hpp @@ -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 { diff --git a/src/system/bsdl_check.cpp b/src/system/bsdl_check.cpp index e200c41..09c99c8 100644 --- a/src/system/bsdl_check.cpp +++ b/src/system/bsdl_check.cpp @@ -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 check_source_conflicts(System *sys) return out; } + +std::vector check_bsdl_completeness(System *sys) +{ + std::vector 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 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; +} diff --git a/src/system/bsdl_check.hpp b/src/system/bsdl_check.hpp index dbc95b2..1447be9 100644 --- a/src/system/bsdl_check.hpp +++ b/src/system/bsdl_check.hpp @@ -36,4 +36,11 @@ std::vector check_jtag_chain(System *sys, const std::vector *nets // schematic. The reverse (BSDL no-connect wired in the netlist) is `NcWired`. std::vector 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 check_bsdl_completeness(System *sys); + #endif // _BSDL_CHECK_HPP_ diff --git a/src/tui/commands.cpp b/src/tui/commands.cpp index fd20707..16dc878 100644 --- a/src/tui/commands.cpp +++ b/src/tui/commands.cpp @@ -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)" }; diff --git a/src/tui/screen_analyze.cpp b/src/tui/screen_analyze.cpp index f03c92b..9a8518b 100644 --- a/src/tui/screen_analyze.cpp +++ b/src/tui/screen_analyze.cpp @@ -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("[") diff --git a/src/tui/screen_dashboard.cpp b/src/tui/screen_dashboard.cpp index 69b4f79..65b0eec 100644 --- a/src/tui/screen_dashboard.cpp +++ b/src/tui/screen_dashboard.cpp @@ -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)")); diff --git a/tests/test_bsdl_apply.cpp b/tests/test_bsdl_apply.cpp index 71fd8e5..34a9de9 100644 --- a/tests/test_bsdl_apply.cpp +++ b/tests/test_bsdl_apply.cpp @@ -1,5 +1,7 @@ #include +#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); +}