From 943b808a75f522fb0fb41bb9b77c6c55b64b9fcd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois?= Date: Wed, 3 Jun 2026 15:07:12 +0200 Subject: [PATCH] BSDL: attach-bsdl command + persist the attached model New `attach-bsdl ` command: parse via BsdlModel, apply_bsdl() onto the part, store the path on Part::bsdl_path, report bound/ unbound. Persist a `B\t` line under the part; on restore, re-parse and re-apply each attached model (the .bsd path is persisted, not the derived pin specs). Round-trip covered by test_bsdl_apply. Co-Authored-By: Claude Opus 4.8 --- src/system/parts.hpp | 1 + src/system/persist.cpp | 19 ++++++++++++++ src/tui/commands.cpp | 52 +++++++++++++++++++++++++++++++++++++++ tests/test_bsdl_apply.cpp | 49 ++++++++++++++++++++++++++++++++++++ 4 files changed, 121 insertions(+) diff --git a/src/system/parts.hpp b/src/system/parts.hpp index 4f301e2..c0c4c2c 100644 --- a/src/system/parts.hpp +++ b/src/system/parts.hpp @@ -16,6 +16,7 @@ public: Module *prnt; ///< Pointer to the parent module. std::string connector_type; ///< Tag used by the transform registry; empty = untyped. ComponentKind kind; ///< Inferred from the part name's reference-designator prefix. + std::string bsdl_path; ///< Path to an attached BSDL (.bsd) model; "" if none. Re-applied on restore. void add(Pin *pin) override; }; diff --git a/src/system/persist.cpp b/src/system/persist.cpp index f6d4537..fffcda6 100644 --- a/src/system/persist.cpp +++ b/src/system/persist.cpp @@ -1,5 +1,6 @@ #include "persist.hpp" +#include "bsdl_model.hpp" #include "connect.hpp" #include "modules.hpp" #include "parts.hpp" @@ -43,6 +44,8 @@ bool save_system(const System *sys, const std::string &filename, std::string &er for (auto &pkv : *mod) { Part *p = pkv.second; f << "P\t" << p->name << "\t" << p->connector_type << "\n"; + if (!p->bsdl_path.empty()) + f << "B\t" << p->bsdl_path << "\n"; for (auto &nkv : *p) { Pin *pin = nkv.second; Signal *s = pin->signal(); @@ -161,6 +164,10 @@ System *restore_system(const std::string &filename, std::string &error) Pin *pin1 = p1->get(fs[3]); Pin *pin2 = p2->get(fs[6]); cur_conn->pin_map.emplace_back(pin1, pin2); + } else if (tag == "B") { + if (!cur_part) return fail("B outside part"); + if (fs.size() < 2) return fail("B needs "); + cur_part->bsdl_path = fs[1]; } else { return fail("unknown tag '" + tag + "'"); } @@ -169,5 +176,17 @@ System *restore_system(const std::string &filename, std::string &error) } } + // Re-derive BSDL-sourced pin specs from each attached model: we persisted the + // .bsd path (`B` tag), not the derived data, so re-parse and re-apply here. + // Models that no longer parse are skipped silently. + for (auto &mkv : *sys->modules()) { + for (auto &pkv : *mkv.second) { + Part *p = pkv.second; + if (p->bsdl_path.empty()) continue; + BsdlModel m = BsdlModel::from_file(p->bsdl_path); + if (m.valid()) apply_bsdl(p, m); + } + } + return sys; } diff --git a/src/tui/commands.cpp b/src/tui/commands.cpp index 7aef14c..0221ddc 100644 --- a/src/tui/commands.cpp +++ b/src/tui/commands.cpp @@ -8,6 +8,7 @@ #include "system/parts.hpp" #include "system/persist.hpp" #include "system/pin_role.hpp" +#include "system/bsdl_model.hpp" #include "system/pins.hpp" #include "system/signals.hpp" #include "system/system.hpp" @@ -452,6 +453,57 @@ void Tui::RegisterCommands() { /*scriptable=*/ true, /*interactive=*/ true, }; + + commands["attach-bsdl"] = { + {{"module", Completion::None}, + {"part (name or pattern)", Completion::None}, + {"bsdl file (.bsd path)", Completion::None}}, + [this](const std::vector &args) { + if (!sys) { Print("no system: run 'new' first."); return; } + if (args.size() != 3) { + Print("usage: attach-bsdl "); + return; + } + + Module *mod; + try { mod = sys->modules()->get(args[0]); } + catch (const std::exception &) { + Print("unknown module: " + args[0]); return; + } + Part *prt = nullptr; + try { prt = mod->get(args[1]); } + catch (const std::exception &) { + std::string needle = ToLower(args[1]); + std::vector matches; + for (auto &p : *mod) + if (ToLower(p.first).find(needle) != std::string::npos) + matches.push_back(p.second); + if (matches.size() == 1) prt = matches[0]; + else { + Print(std::to_string(matches.size()) + + " match(es) for part '" + args[1] + "' in " + mod->name); + return; + } + } + + BsdlModel model = BsdlModel::from_file(args[2]); + if (!model.valid()) { + Print("attach-bsdl: cannot parse " + args[2] + + (model.error().empty() ? "" : (": " + model.error()))); + return; + } + BsdlApplyReport r = apply_bsdl(prt, model); + prt->bsdl_path = args[2]; + Print(mod->name + "/" + prt->name + ": attached BSDL '" + model.entity() + + "' — " + std::to_string(r.bound) + "/" + + std::to_string((int)model.ports().size()) + " ports bound" + + (r.unbound ? (", " + std::to_string(r.unbound) + " unbound") : "")); + }, + /*prompt_for_missing=*/ false, + "attach a BSDL (.bsd) model to a part and populate pin specs", + /*scriptable=*/ true, + /*interactive=*/ false, + }; commands["connect"] = { {{"module1", Completion::None}, {"part1 (name or pattern)", Completion::None}, diff --git a/tests/test_bsdl_apply.cpp b/tests/test_bsdl_apply.cpp index c225dbb..71fd8e5 100644 --- a/tests/test_bsdl_apply.cpp +++ b/tests/test_bsdl_apply.cpp @@ -4,6 +4,12 @@ #include "system/parts.hpp" #include "system/pins.hpp" #include "system/pin_spec.hpp" +#include "system/system.hpp" +#include "system/modules.hpp" +#include "system/persist.hpp" + +#include +#include // Minimal synthetic BSDL: 4 TAP pins, one bidir I/O, a power and a ground // linkage pin, each with a PIN_MAP ball. @@ -92,3 +98,46 @@ TEST_CASE("apply_bsdl falls back to the physical pad when names are balls") { CHECK(part.get("C1")->spec.function == PinFunction::Power); // VDD:C1 CHECK(part.get("C2")->spec.function == PinFunction::Ground); // GND:C2 } + +TEST_CASE("attached BSDL path persists and re-applies on restore") { + const char *bsd = "test_attach_demo.bsd"; + const char *snap = "test_attach_snap.essim"; + { std::ofstream o(bsd); o << DEMO_BSDL; } + + { + System sys; + Module *m = sys.modules()->merge("CARD"); + Part *u = new Part("U1"); + for (const char *n : {"TCK", "TDI", "TDO", "TMS", "IO1", "VDD", "GND"}) + u->add(new Pin(n)); + m->add(u); + + BsdlModel model = BsdlModel::from_file(bsd); + REQUIRE(model.valid()); + apply_bsdl(u, model); + u->bsdl_path = bsd; + + std::string err; + REQUIRE(save_system(&sys, snap, err)); + } + + { + std::string err; + System *sys = restore_system(snap, err); + REQUIRE(sys != nullptr); + + Part *u = sys->modules()->get("CARD")->get("U1"); + CHECK(u->bsdl_path == bsd); + // spec re-derived from the model at restore, not stored in the snapshot + CHECK(u->get("VDD")->spec.function == PinFunction::Power); + CHECK(u->get("VDD")->spec.source == SpecSource::Bsdl); + CHECK(u->get("VDD")->expected_signal_type() == SignalType::Power); + CHECK(u->get("TCK")->spec.function == PinFunction::JtagTck); + CHECK(u->get("TCK")->spec.pad == "A1"); + + delete sys; + } + + std::remove(bsd); + std::remove(snap); +}