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 <noreply@anthropic.com>
This commit is contained in:
@@ -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 "?";
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
111
src/system/bsdl_check.cpp
Normal file
111
src/system/bsdl_check.cpp
Normal file
@@ -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 <string>
|
||||
#include <vector>
|
||||
|
||||
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<Pin *> &pins)
|
||||
{
|
||||
std::string s;
|
||||
for (const Pin *p : pins)
|
||||
s += (s.empty() ? "" : ", ") + pin_label(p);
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<Anomaly> check_pin_specs(System *sys)
|
||||
{
|
||||
std::vector<Anomaly> out;
|
||||
if (!sys)
|
||||
return out;
|
||||
|
||||
for (const Net &net : compute_all_nets(sys)) {
|
||||
std::vector<Pin *> pins;
|
||||
std::vector<Signal *> 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<Pin *> 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;
|
||||
}
|
||||
19
src/system/bsdl_check.hpp
Normal file
19
src/system/bsdl_check.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef _BSDL_CHECK_HPP_
|
||||
#define _BSDL_CHECK_HPP_
|
||||
|
||||
#include "analysis.hpp" // Anomaly, AnomalyKind
|
||||
|
||||
#include <vector>
|
||||
|
||||
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<Anomaly> check_pin_specs(System *sys);
|
||||
|
||||
#endif // _BSDL_CHECK_HPP_
|
||||
@@ -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;
|
||||
|
||||
92
tests/test_bsdl_check.cpp
Normal file
92
tests/test_bsdl_check.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#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 <algorithm>
|
||||
#include <vector>
|
||||
|
||||
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<Pin *> pins)
|
||||
{
|
||||
Signal *s = m->signals->merge(signame);
|
||||
for (Pin *p : pins) {
|
||||
s->add(p);
|
||||
p->connect(s);
|
||||
}
|
||||
}
|
||||
|
||||
int count_kind(const std::vector<Anomaly> &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());
|
||||
}
|
||||
Reference in New Issue
Block a user