P3: unify connector layout + BSDL behind one PinModel provider

New PinModel interface (spec_for / layout / source) + a single apply_model(
Part*, const PinModel&) that materialises missing layout pins and sets each
pin's spec only where the model speaks (spec.source != None), so one source
never clobbers another's. ConnectorModel wraps pin_role/pin_layout;
BsdlPinModel wraps a parsed BsdlModel (indexed by port name and physical pad).
set-connector-type and screen_settype now use ConnectorModel + apply_model;
attach-bsdl and the restore re-apply keep calling apply_bsdl, now a thin
adapter over apply_model. Behaviour-preserving: unit tests (73 cases) green and
the real 8-card system re-runs identically (1517/1517 bound, same JTAG
findings). Covered by test_pin_model.

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

View File

@@ -112,30 +112,42 @@ BsdlModel BsdlModel::from_buffer(const std::string &text, const std::string &nam
return from_handle(bsdl_parse_buffer(text.data(), text.size(), name.c_str(), &opts));
}
BsdlPinModel::BsdlPinModel(const BsdlModel &model)
{
for (const auto &port : model.ports()) {
PinSpec s;
s.function = port.function;
s.direction = port.direction;
s.pad = port.pad;
s.source = SpecSource::Bsdl;
if (!port.name.empty()) by_name_[port.name] = s;
if (!port.pad.empty()) by_pad_[port.pad] = s;
}
}
PinSpec BsdlPinModel::spec_for(const std::string &pin_name) const
{
auto byn = by_name_.find(pin_name);
if (byn != by_name_.end())
return byn->second;
auto byp = by_pad_.find(pin_name);
if (byp != by_pad_.end())
return byp->second;
return PinSpec{}; // source == None
}
// Adapter kept for callers that hold a BsdlModel directly (attach-bsdl, the
// restore re-apply pass, tests): bind via the unified apply_model and map the
// report back to port-coverage terms.
BsdlApplyReport apply_bsdl(Part *part, const BsdlModel &model)
{
BsdlPinModel pm(model);
ApplyReport rep = apply_model(part, pm);
BsdlApplyReport r;
if (!part)
return r;
r.pins_total = (int)part->size();
for (const auto &port : model.ports()) {
Pin *pin = nullptr;
if (!port.name.empty() && part->exists(port.name))
pin = part->get(port.name);
else if (!port.pad.empty() && part->exists(port.pad))
pin = part->get(port.pad);
if (!pin) {
++r.unbound;
continue;
}
pin->spec.function = port.function;
pin->spec.direction = port.direction;
pin->spec.pad = port.pad;
pin->spec.source = SpecSource::Bsdl;
++r.bound;
}
r.pins_total = rep.pins_total;
r.bound = rep.set;
int ports = (int)model.ports().size();
r.unbound = ports > rep.set ? ports - rep.set : 0;
return r;
}

View File

@@ -1,9 +1,11 @@
#ifndef _BSDL_MODEL_HPP_
#define _BSDL_MODEL_HPP_
#include "pin_model.hpp"
#include "pin_spec.hpp"
#include <string>
#include <unordered_map>
#include <vector>
class Part;
@@ -39,6 +41,23 @@ private:
std::vector<Port> ports_;
};
// Adapts a parsed BSDL device to the PinModel interface, indexing its ports by
// both logical name and physical pad (so a netlist that names IC pins either by
// signal or by ball both resolve). `layout()` is empty — BSDL drives specs, not
// pin materialisation, since the netlist's pin naming may differ from the port
// names.
class BsdlPinModel : public PinModel {
public:
explicit BsdlPinModel(const BsdlModel &model);
PinSpec spec_for(const std::string &pin_name) const override;
std::vector<std::string> layout() const override { return {}; }
SpecSource source() const override { return SpecSource::Bsdl; }
private:
std::unordered_map<std::string, PinSpec> by_name_;
std::unordered_map<std::string, PinSpec> by_pad_;
};
// Outcome of binding a model onto a Part's pins.
struct BsdlApplyReport {
int pins_total = 0;

53
src/system/pin_model.cpp Normal file
View File

@@ -0,0 +1,53 @@
#include "pin_model.hpp"
#include "parts.hpp"
#include "pin_name.hpp"
#include "pin_role.hpp"
#include "pins.hpp"
#include <unordered_set>
ApplyReport apply_model(Part *part, const PinModel &model)
{
ApplyReport r;
if (!part)
return r;
// 1. Materialise canonical pins absent from the netlist (deduped by
// canonical name, like FillPartFromLayout). No-op when layout() is empty.
std::vector<std::string> layout = model.layout();
if (!layout.empty()) {
std::unordered_set<std::string> existing;
for (auto &kv : *part)
existing.insert(canonical_pin_name(kv.first));
for (const std::string &name : layout) {
if (existing.count(canonical_pin_name(name)))
continue;
if (part->exists(name))
continue;
part->add(new Pin(name));
++r.materialised;
}
}
// 2. Set each pin's spec where the model speaks for it.
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;
}
}
return r;
}
PinSpec ConnectorModel::spec_for(const std::string &pin_name) const
{
return pin_role(kind_, pin_name);
}
std::vector<std::string> ConnectorModel::layout() const
{
return pin_layout(kind_);
}

55
src/system/pin_model.hpp Normal file
View File

@@ -0,0 +1,55 @@
#ifndef _PIN_MODEL_HPP_
#define _PIN_MODEL_HPP_
#include "pin_spec.hpp"
#include <string>
#include <utility>
#include <vector>
class Part;
// A source of *expected* pin attributes for a part — a connector layout, a BSDL
// device, (later) a SPICE/Modelica model. Both of today's sources implement this
// interface and feed the single `apply_model()`, so `verify` stays agnostic of
// where a pin's spec came from.
struct PinModel {
virtual ~PinModel() = default;
// Expected spec for the pin identified by `pin_name` — its logical name, and
// for models that index physical pads, its package ball too. Returns a
// default PinSpec (source == None) when the model has nothing to say.
virtual PinSpec spec_for(const std::string &pin_name) const = 0;
// Canonical full pin-name list, used to materialise pins absent from the
// imported netlist. Empty means the model does not drive materialisation.
virtual std::vector<std::string> layout() const = 0;
// Which source this model represents (recorded in each spec it sets).
virtual SpecSource source() const = 0;
};
struct ApplyReport {
int pins_total = 0; ///< pins on the part after materialisation
int set = 0; ///< pins whose spec the model set
int materialised = 0; ///< pins created from layout()
};
// Materialise the layout pins missing from the part, then set each pin's `spec`
// from the model — only where the model actually speaks for that pin
// (`spec.source != None`), so a model never wipes another source's spec.
ApplyReport apply_model(Part *part, const PinModel &model);
// Connector-layout model: wraps `pin_role(kind, name)` / `pin_layout(kind)`.
class ConnectorModel : public PinModel {
public:
explicit ConnectorModel(std::string kind) : kind_(std::move(kind)) {}
PinSpec spec_for(const std::string &pin_name) const override;
std::vector<std::string> layout() const override;
SpecSource source() const override { return SpecSource::ConnectorModel; }
private:
std::string kind_;
};
#endif // _PIN_MODEL_HPP_

View File

@@ -8,6 +8,7 @@
#include "system/parts.hpp"
#include "system/persist.hpp"
#include "system/pin_role.hpp"
#include "system/pin_model.hpp"
#include "system/bsdl_model.hpp"
#include "system/bsdl_check.hpp"
#include "system/pins.hpp"
@@ -455,13 +456,12 @@ void Tui::RegisterCommands() {
return;
}
prt->connector_type = args[2];
int filled = FillPartFromLayout(prt, args[2]);
for (auto &kv : *prt)
kv.second->spec = pin_role(args[2], kv.first);
ConnectorModel model(args[2]);
ApplyReport rep = apply_model(prt, model);
Print(mod->name + "/" + prt->name + ": connector_type = "
+ (args[2].empty() ? "(none)" : args[2]));
if (filled > 0)
Print("set-connector-type: materialised " + std::to_string(filled)
if (rep.materialised > 0)
Print("set-connector-type: materialised " + std::to_string(rep.materialised)
+ " NC pin(s) from connector layout");
},
/*prompt_for_missing=*/ false,

View File

@@ -3,6 +3,7 @@
#include "system/modules.hpp"
#include "system/parts.hpp"
#include "system/pin_model.hpp"
#include "system/pin_role.hpp"
#include "system/pins.hpp"
#include "system/system.hpp"
@@ -68,8 +69,8 @@ Component Tui::BuildSettypeScreen() {
return;
}
prt->connector_type = settype_type;
for (auto &kv : *prt)
kv.second->spec = pin_role(settype_type, kv.first);
ConnectorModel model(settype_type);
apply_model(prt, model);
std::string msg = mod->name + "/" + prt->name + " = "
+ (settype_type.empty() ? "(none)" : settype_type);
settype_status = "applied: " + msg;