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:
@@ -26,6 +26,14 @@ FetchContent_MakeAvailable(ftxui)
|
|||||||
find_package(libzip REQUIRED)
|
find_package(libzip REQUIRED)
|
||||||
find_package(pugixml REQUIRED)
|
find_package(pugixml REQUIRED)
|
||||||
|
|
||||||
|
# libbsdl — standalone BSDL parser (LGPL-2.1), dynamically linked from essim
|
||||||
|
# (EUPL-1.2, which the LGPL permits). Path overridable via -DBSDL_DIR=...;
|
||||||
|
# its CLI and tests are not needed inside essim's build.
|
||||||
|
set(BSDL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../libbsdl" CACHE PATH "libbsdl source tree")
|
||||||
|
set(BSDL_BUILD_CLI OFF CACHE BOOL "" FORCE)
|
||||||
|
set(BSDL_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||||
|
add_subdirectory(${BSDL_DIR} ${CMAKE_BINARY_DIR}/libbsdl)
|
||||||
|
|
||||||
# Library target = everything except main.cpp; reused by `essim` and `essim_tests`.
|
# Library target = everything except main.cpp; reused by `essim` and `essim_tests`.
|
||||||
file(GLOB_RECURSE LIB_SOURCES "src/*.cpp")
|
file(GLOB_RECURSE LIB_SOURCES "src/*.cpp")
|
||||||
list(REMOVE_ITEM LIB_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp")
|
list(REMOVE_ITEM LIB_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp")
|
||||||
@@ -39,6 +47,7 @@ target_link_libraries(essim_lib
|
|||||||
ftxui::component
|
ftxui::component
|
||||||
libzip::zip
|
libzip::zip
|
||||||
pugixml::pugixml
|
pugixml::pugixml
|
||||||
|
bsdl::bsdl
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(essim src/main.cpp)
|
add_executable(essim src/main.cpp)
|
||||||
|
|||||||
141
src/system/bsdl_model.cpp
Normal file
141
src/system/bsdl_model.cpp
Normal 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
54
src/system/bsdl_model.hpp
Normal 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_
|
||||||
94
tests/test_bsdl_apply.cpp
Normal file
94
tests/test_bsdl_apply.cpp
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
#include <doctest/doctest.h>
|
||||||
|
|
||||||
|
#include "system/bsdl_model.hpp"
|
||||||
|
#include "system/parts.hpp"
|
||||||
|
#include "system/pins.hpp"
|
||||||
|
#include "system/pin_spec.hpp"
|
||||||
|
|
||||||
|
// Minimal synthetic BSDL: 4 TAP pins, one bidir I/O, a power and a ground
|
||||||
|
// linkage pin, each with a PIN_MAP ball.
|
||||||
|
static const char DEMO_BSDL[] =
|
||||||
|
"entity DEMO is\n"
|
||||||
|
" generic (PHYSICAL_PIN_MAP : string := \"PKG\");\n"
|
||||||
|
" port (\n"
|
||||||
|
" TCK:in bit; TDI:in bit; TDO:out bit; TMS:in bit;\n"
|
||||||
|
" IO1:inout bit;\n"
|
||||||
|
" VDD:linkage bit; GND:linkage bit\n"
|
||||||
|
" );\n"
|
||||||
|
" attribute PIN_MAP of DEMO : entity is PHYSICAL_PIN_MAP;\n"
|
||||||
|
" constant PKG : PIN_MAP_STRING :=\n"
|
||||||
|
" \"TCK:A1,\" & \"TDI:A2,\" & \"TDO:A3,\" & \"TMS:A4,\" &\n"
|
||||||
|
" \"IO1:B1,\" & \"VDD:C1,\" & \"GND:C2\";\n"
|
||||||
|
" attribute TAP_SCAN_IN of TDI : signal is true;\n"
|
||||||
|
" attribute TAP_SCAN_MODE of TMS : signal is true;\n"
|
||||||
|
" attribute TAP_SCAN_OUT of TDO : signal is true;\n"
|
||||||
|
" attribute TAP_SCAN_CLOCK of TCK : signal is (10.0e6, BOTH);\n"
|
||||||
|
" attribute INSTRUCTION_LENGTH of DEMO : entity is 2;\n"
|
||||||
|
" attribute INSTRUCTION_OPCODE of DEMO : entity is \"BYPASS (11), IDCODE (10)\";\n"
|
||||||
|
" attribute IDCODE_REGISTER of DEMO : entity is \"00010010001101000101000000001111\";\n"
|
||||||
|
" attribute BOUNDARY_LENGTH of DEMO : entity is 1;\n"
|
||||||
|
" attribute BOUNDARY_REGISTER of DEMO : entity is \"0 (BC_1, IO1, bidir, X)\";\n"
|
||||||
|
"end DEMO;\n";
|
||||||
|
|
||||||
|
TEST_CASE("BsdlModel parses a buffer into essim pin vocabulary") {
|
||||||
|
BsdlModel m = BsdlModel::from_buffer(DEMO_BSDL, "demo.bsd");
|
||||||
|
REQUIRE(m.valid());
|
||||||
|
CHECK(m.entity() == "DEMO");
|
||||||
|
CHECK(m.ports().size() == 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("apply_bsdl binds by port name and sets each pin spec") {
|
||||||
|
BsdlModel m = BsdlModel::from_buffer(DEMO_BSDL, "demo.bsd");
|
||||||
|
REQUIRE(m.valid());
|
||||||
|
|
||||||
|
Part part("U1");
|
||||||
|
for (const char *n : {"TCK", "TDI", "TDO", "TMS", "IO1", "VDD", "GND"})
|
||||||
|
part.add(new Pin(n));
|
||||||
|
|
||||||
|
BsdlApplyReport r = apply_bsdl(&part, m);
|
||||||
|
CHECK(r.pins_total == 7);
|
||||||
|
CHECK(r.bound == 7);
|
||||||
|
CHECK(r.unbound == 0);
|
||||||
|
|
||||||
|
Pin *tck = part.get("TCK");
|
||||||
|
CHECK(tck->spec.function == PinFunction::JtagTck);
|
||||||
|
CHECK(tck->spec.direction == PinDirection::In);
|
||||||
|
CHECK(tck->spec.source == SpecSource::Bsdl);
|
||||||
|
CHECK(tck->spec.pad == "A1");
|
||||||
|
|
||||||
|
CHECK(part.get("TDI")->spec.function == PinFunction::JtagTdi);
|
||||||
|
CHECK(part.get("TDO")->spec.function == PinFunction::JtagTdo);
|
||||||
|
CHECK(part.get("TDO")->spec.direction == PinDirection::Out);
|
||||||
|
CHECK(part.get("TMS")->spec.function == PinFunction::JtagTms);
|
||||||
|
|
||||||
|
Pin *io1 = part.get("IO1");
|
||||||
|
CHECK(io1->spec.function == PinFunction::Signal);
|
||||||
|
CHECK(io1->spec.direction == PinDirection::Bidir);
|
||||||
|
|
||||||
|
// Linkage pins classified, and the derived expected net type follows.
|
||||||
|
Pin *vdd = part.get("VDD");
|
||||||
|
CHECK(vdd->spec.function == PinFunction::Power);
|
||||||
|
CHECK(vdd->spec.direction == PinDirection::Power);
|
||||||
|
CHECK(vdd->expected_signal_type() == SignalType::Power);
|
||||||
|
|
||||||
|
Pin *gnd = part.get("GND");
|
||||||
|
CHECK(gnd->spec.function == PinFunction::Ground);
|
||||||
|
CHECK(gnd->expected_signal_type() == SignalType::GndShield);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("apply_bsdl falls back to the physical pad when names are balls") {
|
||||||
|
BsdlModel m = BsdlModel::from_buffer(DEMO_BSDL, "demo.bsd");
|
||||||
|
REQUIRE(m.valid());
|
||||||
|
|
||||||
|
// A part whose pins are named by package ball, not by signal.
|
||||||
|
Part part("U2");
|
||||||
|
for (const char *n : {"A1", "A2", "A3", "A4", "B1", "C1", "C2"})
|
||||||
|
part.add(new Pin(n));
|
||||||
|
|
||||||
|
BsdlApplyReport r = apply_bsdl(&part, m);
|
||||||
|
CHECK(r.bound == 7);
|
||||||
|
CHECK(r.unbound == 0);
|
||||||
|
CHECK(part.get("A1")->spec.function == PinFunction::JtagTck); // TCK:A1
|
||||||
|
CHECK(part.get("C1")->spec.function == PinFunction::Power); // VDD:C1
|
||||||
|
CHECK(part.get("C2")->spec.function == PinFunction::Ground); // GND:C2
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user