P3.2: source precedence + model-vs-netlist conflict check

Rank the spec sources (spec_source_rank: UserOverride > Bsdl > ConnectorModel
> Inferred > Imported); apply_model now refuses to overwrite a spec owned by a
higher-rank source, so one model never clobbers a more authoritative one. New
check_source_conflicts(System*) emits SourceConflict for a pin the BSDL
declares power/ground (a must-connect rail) that the netlist leaves
unconnected — a rail floated in the schematic; surfaced as a sixth `verify`
pass. Unit tests (75 cases) green; the real 8-card system reports 0 conflicts
(its rails are all connected) while the JTAG findings remain.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 16:08:28 +02:00
parent cb61e9b084
commit fe5b2c3d96
10 changed files with 120 additions and 8 deletions

View File

@@ -29,6 +29,7 @@ const char *anomaly_kind_name(AnomalyKind k) {
case AnomalyKind::JtagTapIncomplete: return "jtag-tap-incomplete";
case AnomalyKind::JtagChainBreak: return "jtag-chain-break";
case AnomalyKind::JtagBusUnbridged: return "jtag-bus-unbridged";
case AnomalyKind::SourceConflict: return "source-conflict";
}
return "?";
}

View File

@@ -37,6 +37,7 @@ enum class AnomalyKind {
JtagTapIncomplete, ///< A TAP device is missing one of TDI/TDO/TMS/TCK.
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).
};
struct Anomaly {

View File

@@ -1,5 +1,6 @@
#include "bsdl_check.hpp"
#include "connect.hpp"
#include "modules.hpp"
#include "nets.hpp"
#include "parts.hpp"
@@ -10,6 +11,7 @@
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
@@ -277,3 +279,43 @@ std::vector<Anomaly> check_jtag_chain(System *sys)
return out;
}
std::vector<Anomaly> check_source_conflicts(System *sys)
{
std::vector<Anomaly> out;
if (!sys)
return out;
// Pins bridged to a peer signal through a connection count as connected.
std::unordered_set<Pin *> bridged;
for (auto &ckv : *sys->connections())
for (auto &wp : ckv.second->pin_map) {
if (wp.first) bridged.insert(wp.first);
if (wp.second) bridged.insert(wp.second);
}
for (auto &mkv : *sys->modules())
for (auto &pkv : *mkv.second)
for (auto &nkv : *pkv.second) {
Pin *pin = nkv.second;
if (pin->spec.source != SpecSource::Bsdl)
continue;
PinFunction f = pin->spec.function;
if (f != PinFunction::Power && f != PinFunction::Ground)
continue; // only must-connect rails are a clear defect
if (pin->connected() || bridged.count(pin))
continue;
Anomaly a;
a.kind = AnomalyKind::SourceConflict;
a.module = (pin->prnt) ? pin->prnt->prnt : nullptr;
a.message = pin_label(pin) + ": BSDL says "
+ (f == PinFunction::Power ? "power" : "ground")
+ " but the netlist leaves it unconnected"
+ (pin->nc_origin == NcOrigin::ImportedUnconnected
? " (marked NC at import)" : "");
out.push_back(std::move(a));
}
return out;
}

View File

@@ -25,4 +25,10 @@ std::vector<Anomaly> check_pin_specs(System *sys);
// Empty when the system has no TAP pins.
std::vector<Anomaly> check_jtag_chain(System *sys);
// Conflicts between a device model and the netlist's own view of a pin. Today:
// a pin the BSDL declares power/ground (a must-connect rail) that the netlist
// leaves unconnected (no signal and not bridged) — i.e. a rail floated in the
// schematic. The reverse (BSDL no-connect wired in the netlist) is `NcWired`.
std::vector<Anomaly> check_source_conflicts(System *sys);
#endif // _BSDL_CHECK_HPP_

View File

@@ -30,14 +30,17 @@ ApplyReport apply_model(Part *part, const PinModel &model)
}
}
// 2. Set each pin's spec where the model speaks for it.
// 2. Set each pin's spec where the model speaks for it — but never overwrite
// a spec already owned by a higher-precedence source (see spec_source_rank).
r.pins_total = (int)part->size();
for (auto &kv : *part) {
PinSpec s = model.spec_for(kv.first);
if (s.source != SpecSource::None) {
kv.second->spec = s;
++r.set;
}
if (s.source == SpecSource::None)
continue;
if (spec_source_rank(s.source) < spec_source_rank(kv.second->spec.source))
continue;
kv.second->spec = s;
++r.set;
}
return r;
}

View File

@@ -51,4 +51,21 @@ inline PinFunction function_from_signal_type(SignalType t)
}
}
// Precedence of spec sources: a higher rank wins when two sources speak for the
// same pin. A user override beats any model; a device model (BSDL) beats a
// connector layout; both beat plain import / inference. Used by apply_model to
// avoid clobbering a more authoritative spec.
inline int spec_source_rank(SpecSource s)
{
switch (s) {
case SpecSource::None: return 0;
case SpecSource::Imported: return 1;
case SpecSource::Inferred: return 2;
case SpecSource::ConnectorModel: return 3;
case SpecSource::Bsdl: return 4;
case SpecSource::UserOverride: return 5;
}
return 0;
}
#endif // _PIN_SPEC_HPP_

View File

@@ -312,6 +312,13 @@ void Tui::RegisterCommands() {
Print(" [" + std::string(anomaly_kind_name(a.kind)) + "] " + a.message);
Print("verify: " + std::to_string(jtag_anoms.size())
+ " JTAG chain anomaly(ies).");
// Model-vs-netlist conflicts (e.g. a BSDL power pin left unconnected).
auto conflict_anoms = check_source_conflicts(sys.get());
for (const auto &a : conflict_anoms)
Print(" [" + std::string(anomaly_kind_name(a.kind)) + "] " + a.message);
Print("verify: " + std::to_string(conflict_anoms.size())
+ " source-conflict(s).");
}, true,
"check pin roles, bridged-net consistency, and model-driven pin specs (contention/undriven/NC)" };