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:
2026-06-03 15:15:11 +02:00
parent 943b808a75
commit 5caa4c530d
6 changed files with 241 additions and 4 deletions

111
src/system/bsdl_check.cpp Normal file
View 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;
}