From 5caa4c530db1f93a0f868e992741f26d0d480c1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Wed, 3 Jun 2026 15:15:11 +0200 Subject: [PATCH] verify: model-driven pin checks (contention / undriven / NC-wired) New bsdl_check.{hpp,cpp}: check_pin_specs(System*) walks the nets and uses each pin's PinSpec direction/function to flag DriveContention (>=2 push-pull output drivers), UndrivenNet (a multi-pin net with input(s) but no driver), and NcWired (a no-connect pin wired onto a multi-pin net). Added as a pass in `verify`; AnomalyKind extended accordingly. Nets with no direction data are skipped, so un-modelled parts produce no noise. Covered by test_bsdl_check. Co-Authored-By: Claude Opus 4.8 --- src/system/analysis.cpp | 9 ++-- src/system/analysis.hpp | 3 ++ src/system/bsdl_check.cpp | 111 ++++++++++++++++++++++++++++++++++++++ src/system/bsdl_check.hpp | 19 +++++++ src/tui/commands.cpp | 11 +++- tests/test_bsdl_check.cpp | 92 +++++++++++++++++++++++++++++++ 6 files changed, 241 insertions(+), 4 deletions(-) create mode 100644 src/system/bsdl_check.cpp create mode 100644 src/system/bsdl_check.hpp create mode 100644 tests/test_bsdl_check.cpp diff --git a/src/system/analysis.cpp b/src/system/analysis.cpp index 2fcde92..7e1b2f8 100644 --- a/src/system/analysis.cpp +++ b/src/system/analysis.cpp @@ -20,9 +20,12 @@ const char *group_kind_name(GroupKind k) { const char *anomaly_kind_name(AnomalyKind k) { switch (k) { - case AnomalyKind::DiffPairOrphan: return "diff-pair-orphan"; - case AnomalyKind::BusGap: return "bus-gap"; - case AnomalyKind::DiffBusGap: return "diff-bus-gap"; + case AnomalyKind::DiffPairOrphan: return "diff-pair-orphan"; + case AnomalyKind::BusGap: return "bus-gap"; + case AnomalyKind::DiffBusGap: return "diff-bus-gap"; + case AnomalyKind::DriveContention: return "drive-contention"; + case AnomalyKind::UndrivenNet: return "undriven-net"; + case AnomalyKind::NcWired: return "nc-wired"; } return "?"; } diff --git a/src/system/analysis.hpp b/src/system/analysis.hpp index d854596..2cbabea 100644 --- a/src/system/analysis.hpp +++ b/src/system/analysis.hpp @@ -31,6 +31,9 @@ enum class AnomalyKind { DiffPairOrphan, ///< X_P present without X_N (or vice versa). BusGap, ///< NAME[0..N] has a missing index inside the range. DiffBusGap, ///< Diff bus MDI[0..3]_P/N is missing one of its lanes. + DriveContention, ///< A net has ≥2 push-pull output (Out) drivers. + UndrivenNet, ///< A net has input(s) but no driver (Out/Bidir/Power). + NcWired, ///< A no-connect pin is wired onto a multi-pin net. }; struct Anomaly { diff --git a/src/system/bsdl_check.cpp b/src/system/bsdl_check.cpp new file mode 100644 index 0000000..9da0009 --- /dev/null +++ b/src/system/bsdl_check.cpp @@ -0,0 +1,111 @@ +#include "bsdl_check.hpp" + +#include "modules.hpp" +#include "nets.hpp" +#include "parts.hpp" +#include "pin_spec.hpp" +#include "pins.hpp" +#include "signals.hpp" +#include "system.hpp" + +#include +#include + +namespace { + +std::string pin_label(const Pin *p) +{ + std::string part = (p && p->prnt) ? p->prnt->name : std::string("?"); + return part + "/" + (p ? p->name : std::string("?")); +} + +std::string join_labels(const std::vector &pins) +{ + std::string s; + for (const Pin *p : pins) + s += (s.empty() ? "" : ", ") + pin_label(p); + return s; +} + +} // namespace + +std::vector check_pin_specs(System *sys) +{ + std::vector out; + if (!sys) + return out; + + for (const Net &net : compute_all_nets(sys)) { + std::vector pins; + std::vector sigs; + Module *mod = nullptr; + std::string label; + + for (const auto &mp : net.members) { + if (!mod) + mod = mp.first; + sigs.push_back(mp.second); + if (label.empty() && mp.first && mp.second) + label = mp.first->name + "/" + mp.second->name; + for (auto &kv : *mp.second) + pins.push_back(kv.second); + } + + if (pins.size() < 2) + continue; // singleton net — nothing to compare + + int outs = 0, drivers = 0, ins = 0, known = 0; + std::vector out_pins, in_pins, nc_pins; + + for (Pin *p : pins) { + const PinSpec &s = p->spec; + if (s.function == PinFunction::NoConnect) + nc_pins.push_back(p); + if (s.direction == PinDirection::Unknown) + continue; + ++known; + switch (s.direction) { + case PinDirection::Out: ++outs; ++drivers; out_pins.push_back(p); break; + case PinDirection::Bidir: ++drivers; break; + case PinDirection::Power: ++drivers; break; + case PinDirection::In: ++ins; in_pins.push_back(p); break; + default: break; + } + } + + // A no-connect pin that is nonetheless on a multi-pin net. + for (Pin *p : nc_pins) { + Anomaly a; + a.kind = AnomalyKind::NcWired; + a.module = mod; + a.involved = sigs; + a.message = pin_label(p) + " is marked no-connect but wired to net " + label; + out.push_back(std::move(a)); + } + + if (!known) + continue; // no direction data on this net — skip drive checks + + if (outs >= 2) { + Anomaly a; + a.kind = AnomalyKind::DriveContention; + a.module = mod; + a.involved = sigs; + a.message = "net " + label + ": " + std::to_string(outs) + + " output drivers (" + join_labels(out_pins) + ")"; + out.push_back(std::move(a)); + } + + if (ins >= 1 && drivers == 0) { + Anomaly a; + a.kind = AnomalyKind::UndrivenNet; + a.module = mod; + a.involved = sigs; + a.message = "net " + label + ": input(s) with no driver (" + + join_labels(in_pins) + ")"; + out.push_back(std::move(a)); + } + } + + return out; +} diff --git a/src/system/bsdl_check.hpp b/src/system/bsdl_check.hpp new file mode 100644 index 0000000..221cc54 --- /dev/null +++ b/src/system/bsdl_check.hpp @@ -0,0 +1,19 @@ +#ifndef _BSDL_CHECK_HPP_ +#define _BSDL_CHECK_HPP_ + +#include "analysis.hpp" // Anomaly, AnomalyKind + +#include + +class System; + +// Model-driven pin checks over the system's nets, using the PinSpec +// direction/function populated by connector or BSDL models. Emits: +// - DriveContention : a net with ≥2 push-pull output drivers; +// - UndrivenNet : a multi-pin net with input(s) but no driver; +// - NcWired : a no-connect pin wired onto a multi-pin net. +// Read-only; nets with no direction data are skipped (no false positives on +// un-modelled parts). +std::vector check_pin_specs(System *sys); + +#endif // _BSDL_CHECK_HPP_ diff --git a/src/tui/commands.cpp b/src/tui/commands.cpp index 0221ddc..f43dd98 100644 --- a/src/tui/commands.cpp +++ b/src/tui/commands.cpp @@ -9,6 +9,7 @@ #include "system/persist.hpp" #include "system/pin_role.hpp" #include "system/bsdl_model.hpp" +#include "system/bsdl_check.hpp" #include "system/pins.hpp" #include "system/signals.hpp" #include "system/system.hpp" @@ -295,8 +296,16 @@ void Tui::RegisterCommands() { + " orphan pin(s) at import (" + std::to_string(orph_imported) + " imported NC, " + std::to_string(orph_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()); + 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)."); }, true, - "check pin roles locally and signal-type consistency across bridged nets" }; + "check pin roles, bridged-net consistency, and model-driven pin specs (contention/undriven/NC)" }; commands["dashboard"] = { {}, [this](auto &) { screen_idx = 4; diff --git a/tests/test_bsdl_check.cpp b/tests/test_bsdl_check.cpp new file mode 100644 index 0000000..a0abaa9 --- /dev/null +++ b/tests/test_bsdl_check.cpp @@ -0,0 +1,92 @@ +#include + +#include "system/analysis.hpp" +#include "system/bsdl_check.hpp" +#include "system/modules.hpp" +#include "system/parts.hpp" +#include "system/pin_spec.hpp" +#include "system/pins.hpp" +#include "system/signals.hpp" +#include "system/system.hpp" + +#include +#include + +namespace { + +Pin *mkpin(Part *part, const char *name, PinDirection d, PinFunction f) +{ + Pin *p = new Pin(name); + p->spec.direction = d; + p->spec.function = f; + p->spec.source = SpecSource::Bsdl; + part->add(p); + return p; +} + +void wire(Module *m, const char *signame, std::vector pins) +{ + Signal *s = m->signals->merge(signame); + for (Pin *p : pins) { + s->add(p); + p->connect(s); + } +} + +int count_kind(const std::vector &v, AnomalyKind k) +{ + return (int)std::count_if(v.begin(), v.end(), + [&](const Anomaly &a) { return a.kind == k; }); +} + +} // namespace + +TEST_CASE("check_pin_specs flags contention, undriven nets and NC-wired pins") { + System sys; + Module *m = sys.modules()->merge("M"); + Part *u1 = new Part("U1"); + Part *u2 = new Part("U2"); + m->add(u1); + m->add(u2); + + // Two push-pull outputs on one net → contention. + wire(m, "NET_CONT", {mkpin(u1, "O1", PinDirection::Out, PinFunction::Signal), + mkpin(u2, "O2", PinDirection::Out, PinFunction::Signal)}); + + // Two inputs, no driver → undriven. + wire(m, "NET_UNDR", {mkpin(u1, "I1", PinDirection::In, PinFunction::Signal), + mkpin(u2, "I2", PinDirection::In, PinFunction::Signal)}); + + // A no-connect pin wired to a driver → nc-wired (and not undriven: the + // NC pin is Power-direction here, so the net has a driver). + wire(m, "NET_NC", {mkpin(u1, "NC1", PinDirection::Power, PinFunction::NoConnect), + mkpin(u2, "D1", PinDirection::Out, PinFunction::Signal)}); + + // Healthy: one driver + one input → no anomaly. + wire(m, "NET_OK", {mkpin(u1, "OD", PinDirection::Out, PinFunction::Signal), + mkpin(u2, "ID", PinDirection::In, PinFunction::Signal)}); + + auto anoms = check_pin_specs(&sys); + CHECK(count_kind(anoms, AnomalyKind::DriveContention) == 1); + CHECK(count_kind(anoms, AnomalyKind::UndrivenNet) == 1); + CHECK(count_kind(anoms, AnomalyKind::NcWired) == 1); +} + +TEST_CASE("check_pin_specs stays silent on un-modelled nets") { + System sys; + Module *m = sys.modules()->merge("M"); + Part *u1 = new Part("U1"); + Part *u2 = new Part("U2"); + m->add(u1); + m->add(u2); + + // No specs set (direction Unknown / function Unknown) → no findings. + Pin *a = new Pin("A"); + Pin *b = new Pin("B"); + u1->add(a); + u2->add(b); + wire(m, "NET", {a, b}); + + auto anoms = check_pin_specs(&sys); + CHECK(anoms.empty()); +}