BSDL: ingest libbsdl into essim and populate PinSpec from a device model

Link libbsdl dynamically (add_subdirectory ../libbsdl, overridable via
-DBSDL_DIR). New BsdlModel wraps the C ABI and reduces a parsed .bsd to
essim's pin vocabulary; apply_bsdl() binds each port to a Pin (by name, then
by physical pad) and sets its spec: direction, function (TAP role / power /
ground / signal), pad, and source = Bsdl.

This feeds the PinSpec fields from P1, so verify's existing power/ground
placement pass now lights up for BSDL-modelled parts. Covered by
test_bsdl_apply (name + pad binding, TAP roles, linkage classification).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 15:01:00 +02:00
parent 86236d744d
commit 279be513a4
4 changed files with 298 additions and 0 deletions

141
src/system/bsdl_model.cpp Normal file
View File

@@ -0,0 +1,141 @@
#include "bsdl_model.hpp"
#include "parts.hpp"
#include "pins.hpp"
#include <bsdl/bsdl.h>
#include <cctype>
#include <utility>
namespace {
PinDirection map_dir(bsdl_dir_t d)
{
switch (d) {
case BSDL_DIR_IN: return PinDirection::In;
case BSDL_DIR_OUT: return PinDirection::Out;
case BSDL_DIR_INOUT: return PinDirection::Bidir;
case BSDL_DIR_BUFFER: return PinDirection::Out;
case BSDL_DIR_LINKAGE: return PinDirection::Power;
default: return PinDirection::Unknown;
}
}
PinFunction tap_function(bsdl_tap_role_t t)
{
switch (t) {
case BSDL_TAP_TDI: return PinFunction::JtagTdi;
case BSDL_TAP_TDO: return PinFunction::JtagTdo;
case BSDL_TAP_TMS: return PinFunction::JtagTms;
case BSDL_TAP_TCK: return PinFunction::JtagTck;
case BSDL_TAP_TRST: return PinFunction::JtagTrst;
default: return PinFunction::Unknown;
}
}
std::string to_upper(std::string s)
{
for (char &c : s) c = (char)std::toupper((unsigned char)c);
return s;
}
bool starts_with(const std::string &u, const char *p)
{
return u.rfind(p, 0) == 0;
}
// Classify a BSDL `linkage` port (power / ground / NC) by name. Linkage lumps
// these together; only the name distinguishes them.
PinFunction linkage_function(const std::string &name)
{
std::string u = to_upper(name);
if (starts_with(u, "GND") || starts_with(u, "VSS"))
return PinFunction::Ground;
if (starts_with(u, "VDD") || starts_with(u, "VCC") || starts_with(u, "VEE") ||
starts_with(u, "VPP") || starts_with(u, "VREF") || starts_with(u, "VBAT") ||
starts_with(u, "VAA") ||
(u.size() >= 2 && u[0] == 'V' && std::isdigit((unsigned char)u[1])))
return PinFunction::Power;
if (starts_with(u, "NC"))
return PinFunction::NoConnect;
return PinFunction::Unknown;
}
PinFunction derive_function(const bsdl_pin_t &p)
{
if (p.tap_role != BSDL_TAP_NONE)
return tap_function(p.tap_role);
if (p.dir == BSDL_DIR_LINKAGE)
return linkage_function(p.name ? p.name : "");
return PinFunction::Signal; // in / out / inout / buffer
}
} // namespace
BsdlModel BsdlModel::from_handle(struct bsdl *d)
{
BsdlModel m;
if (!d) {
m.error_ = "BSDL parse failed";
return m;
}
m.ok_ = true;
m.entity_ = d->entity ? d->entity : "";
m.ports_.reserve(d->pin_count);
for (size_t i = 0; i < d->pin_count; ++i) {
const bsdl_pin_t &p = d->pins[i];
Port port;
port.name = p.name ? p.name : "";
port.direction = map_dir(p.dir);
port.function = derive_function(p);
port.pad = p.physical_pin ? p.physical_pin : "";
m.ports_.push_back(std::move(port));
}
bsdl_free(d);
return m;
}
BsdlModel BsdlModel::from_file(const std::string &path)
{
bsdl_opts_t opts;
bsdl_opts_default(&opts);
return from_handle(bsdl_parse_file(path.c_str(), &opts));
}
BsdlModel BsdlModel::from_buffer(const std::string &text, const std::string &name)
{
bsdl_opts_t opts;
bsdl_opts_default(&opts);
return from_handle(bsdl_parse_buffer(text.data(), text.size(), name.c_str(), &opts));
}
BsdlApplyReport apply_bsdl(Part *part, const BsdlModel &model)
{
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;
}
return r;
}

54
src/system/bsdl_model.hpp Normal file
View File

@@ -0,0 +1,54 @@
#ifndef _BSDL_MODEL_HPP_
#define _BSDL_MODEL_HPP_
#include "pin_spec.hpp"
#include <string>
#include <vector>
class Part;
struct bsdl; // libbsdl C model — opaque here.
// A parsed BSDL device reduced to essim's pin vocabulary. Value type: the
// underlying libbsdl C model is consumed and freed during construction, so a
// BsdlModel carries no C resources and copies/moves freely.
class BsdlModel
{
public:
struct Port {
std::string name; ///< Logical BSDL port name.
PinDirection direction = PinDirection::Unknown;
PinFunction function = PinFunction::Unknown; ///< TAP role / power / ground / signal.
std::string pad; ///< Physical package pin (PIN_MAP); "" if unmapped.
};
static BsdlModel from_file(const std::string &path);
static BsdlModel from_buffer(const std::string &text, const std::string &name = "bsdl");
bool valid() const { return ok_; }
const std::string &entity() const { return entity_; }
const std::string &error() const { return error_; }
const std::vector<Port> &ports() const { return ports_; }
private:
static BsdlModel from_handle(struct bsdl *d); // consumes (frees) d.
bool ok_ = false;
std::string entity_;
std::string error_;
std::vector<Port> ports_;
};
// Outcome of binding a model onto a Part's pins.
struct BsdlApplyReport {
int pins_total = 0;
int bound = 0; ///< Ports matched to a pin (by name, then by physical pad).
int unbound = 0; ///< Ports with no matching pin on the part.
};
// Set each matched Pin's `spec` (function / direction / pad, source = Bsdl) from
// the model. A port is matched to a pin by port name first, then by physical pad
// — so a netlist that names IC pins either by signal or by ball both bind.
BsdlApplyReport apply_bsdl(Part *part, const BsdlModel &model);
#endif // _BSDL_MODEL_HPP_