diff --git a/CMakeLists.txt b/CMakeLists.txt index e8ae200..04cc668 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,14 @@ FetchContent_MakeAvailable(ftxui) find_package(libzip 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`. file(GLOB_RECURSE LIB_SOURCES "src/*.cpp") list(REMOVE_ITEM LIB_SOURCES "${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp") @@ -39,6 +47,7 @@ target_link_libraries(essim_lib ftxui::component libzip::zip pugixml::pugixml + bsdl::bsdl ) add_executable(essim src/main.cpp) diff --git a/src/system/bsdl_model.cpp b/src/system/bsdl_model.cpp new file mode 100644 index 0000000..f45b27f --- /dev/null +++ b/src/system/bsdl_model.cpp @@ -0,0 +1,141 @@ +#include "bsdl_model.hpp" + +#include "parts.hpp" +#include "pins.hpp" + +#include + +#include +#include + +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; +} diff --git a/src/system/bsdl_model.hpp b/src/system/bsdl_model.hpp new file mode 100644 index 0000000..650db76 --- /dev/null +++ b/src/system/bsdl_model.hpp @@ -0,0 +1,54 @@ +#ifndef _BSDL_MODEL_HPP_ +#define _BSDL_MODEL_HPP_ + +#include "pin_spec.hpp" + +#include +#include + +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 &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 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_ diff --git a/tests/test_bsdl_apply.cpp b/tests/test_bsdl_apply.cpp new file mode 100644 index 0000000..c225dbb --- /dev/null +++ b/tests/test_bsdl_apply.cpp @@ -0,0 +1,94 @@ +#include + +#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 +}