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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
53
src/system/pin_model.cpp
Normal 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
55
src/system/pin_model.hpp
Normal 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_
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user