BSDL: attach-bsdl command + persist the attached model
New `attach-bsdl <module> <part> <file.bsd>` command: parse via BsdlModel, apply_bsdl() onto the part, store the path on Part::bsdl_path, report bound/ unbound. Persist a `B\t<path>` 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 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,7 @@ public:
|
|||||||
Module *prnt; ///< Pointer to the parent module.
|
Module *prnt; ///< Pointer to the parent module.
|
||||||
std::string connector_type; ///< Tag used by the transform registry; empty = untyped.
|
std::string connector_type; ///< Tag used by the transform registry; empty = untyped.
|
||||||
ComponentKind kind; ///< Inferred from the part name's reference-designator prefix.
|
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;
|
void add(Pin *pin) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "persist.hpp"
|
#include "persist.hpp"
|
||||||
|
|
||||||
|
#include "bsdl_model.hpp"
|
||||||
#include "connect.hpp"
|
#include "connect.hpp"
|
||||||
#include "modules.hpp"
|
#include "modules.hpp"
|
||||||
#include "parts.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) {
|
for (auto &pkv : *mod) {
|
||||||
Part *p = pkv.second;
|
Part *p = pkv.second;
|
||||||
f << "P\t" << p->name << "\t" << p->connector_type << "\n";
|
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) {
|
for (auto &nkv : *p) {
|
||||||
Pin *pin = nkv.second;
|
Pin *pin = nkv.second;
|
||||||
Signal *s = pin->signal();
|
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 *pin1 = p1->get(fs[3]);
|
||||||
Pin *pin2 = p2->get(fs[6]);
|
Pin *pin2 = p2->get(fs[6]);
|
||||||
cur_conn->pin_map.emplace_back(pin1, pin2);
|
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 <path>");
|
||||||
|
cur_part->bsdl_path = fs[1];
|
||||||
} else {
|
} else {
|
||||||
return fail("unknown tag '" + tag + "'");
|
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;
|
return sys;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include "system/parts.hpp"
|
#include "system/parts.hpp"
|
||||||
#include "system/persist.hpp"
|
#include "system/persist.hpp"
|
||||||
#include "system/pin_role.hpp"
|
#include "system/pin_role.hpp"
|
||||||
|
#include "system/bsdl_model.hpp"
|
||||||
#include "system/pins.hpp"
|
#include "system/pins.hpp"
|
||||||
#include "system/signals.hpp"
|
#include "system/signals.hpp"
|
||||||
#include "system/system.hpp"
|
#include "system/system.hpp"
|
||||||
@@ -452,6 +453,57 @@ void Tui::RegisterCommands() {
|
|||||||
/*scriptable=*/ true,
|
/*scriptable=*/ true,
|
||||||
/*interactive=*/ 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<std::string> &args) {
|
||||||
|
if (!sys) { Print("no system: run 'new' first."); return; }
|
||||||
|
if (args.size() != 3) {
|
||||||
|
Print("usage: attach-bsdl <module> <part> <file.bsd>");
|
||||||
|
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<Part *> 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"] = {
|
commands["connect"] = {
|
||||||
{{"module1", Completion::None},
|
{{"module1", Completion::None},
|
||||||
{"part1 (name or pattern)", Completion::None},
|
{"part1 (name or pattern)", Completion::None},
|
||||||
|
|||||||
@@ -4,6 +4,12 @@
|
|||||||
#include "system/parts.hpp"
|
#include "system/parts.hpp"
|
||||||
#include "system/pins.hpp"
|
#include "system/pins.hpp"
|
||||||
#include "system/pin_spec.hpp"
|
#include "system/pin_spec.hpp"
|
||||||
|
#include "system/system.hpp"
|
||||||
|
#include "system/modules.hpp"
|
||||||
|
#include "system/persist.hpp"
|
||||||
|
|
||||||
|
#include <cstdio>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
// Minimal synthetic BSDL: 4 TAP pins, one bidir I/O, a power and a ground
|
// Minimal synthetic BSDL: 4 TAP pins, one bidir I/O, a power and a ground
|
||||||
// linkage pin, each with a PIN_MAP ball.
|
// 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("C1")->spec.function == PinFunction::Power); // VDD:C1
|
||||||
CHECK(part.get("C2")->spec.function == PinFunction::Ground); // GND:C2
|
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);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user