Reorganise the tree into business vs frontend as separate directories:
src/core/{domain,imports,app} (was system/, imports/, app/)
src/frontends/tui/ (was tui/ + main.cpp)
tests/tui/ (the FTXUI-coupled helper test)
All cross-dir #include paths rewritten; same-dir includes untouched.
CMake: essim_core is the frontend-agnostic business library — links libzip,
pugixml and bsdl, NO GUI toolkit. Each frontend is a self-contained
src/frontends/<name>/ (own CMakeLists, toolkit, main.cpp) that links
essim_core, selected with -DESSIM_FRONTEND=<name> (default tui; 'none' = core +
tests only, no toolkit fetched). FTXUI moved into the tui frontend. Tests are
split: essim_tests links essim_core (no FTXUI), essim_tui_tests links essim_tui.
Verified: default tui build green (ctest 2/2); ESSIM_FRONTEND=none builds the
core + tests with FTXUI never fetched and no `essim` binary.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
182 lines
5.9 KiB
C++
182 lines
5.9 KiB
C++
#include <doctest/doctest.h>
|
|
|
|
#include "core/domain/analysis.hpp"
|
|
#include "core/domain/bsdl_check.hpp"
|
|
#include "core/domain/modules.hpp"
|
|
#include "core/domain/parts.hpp"
|
|
#include "core/domain/pin_spec.hpp"
|
|
#include "core/domain/pins.hpp"
|
|
#include "core/domain/signals.hpp"
|
|
#include "core/domain/system.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
|
|
Pin *mkpin(Part *part, const char *name, PinDirection d, PinFunction f)
|
|
{
|
|
Pin *p = new Pin(name);
|
|
p->spec.direction = d;
|
|
p->spec.function = f;
|
|
p->spec.source = SpecSource::Bsdl;
|
|
part->add(p);
|
|
return p;
|
|
}
|
|
|
|
void wire(Module *m, const char *signame, std::vector<Pin *> pins)
|
|
{
|
|
Signal *s = m->signals->merge(signame);
|
|
for (Pin *p : pins) {
|
|
s->add(p);
|
|
p->connect(s);
|
|
}
|
|
}
|
|
|
|
int count_kind(const std::vector<Anomaly> &v, AnomalyKind k)
|
|
{
|
|
return (int)std::count_if(v.begin(), v.end(),
|
|
[&](const Anomaly &a) { return a.kind == k; });
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("check_pin_specs flags contention, undriven nets and NC-wired pins") {
|
|
System sys;
|
|
Module *m = sys.modules()->merge("M");
|
|
Part *u1 = new Part("U1");
|
|
Part *u2 = new Part("U2");
|
|
m->add(u1);
|
|
m->add(u2);
|
|
|
|
// Two push-pull outputs on one net → contention.
|
|
wire(m, "NET_CONT", {mkpin(u1, "O1", PinDirection::Out, PinFunction::Signal),
|
|
mkpin(u2, "O2", PinDirection::Out, PinFunction::Signal)});
|
|
|
|
// Two inputs, no driver → undriven.
|
|
wire(m, "NET_UNDR", {mkpin(u1, "I1", PinDirection::In, PinFunction::Signal),
|
|
mkpin(u2, "I2", PinDirection::In, PinFunction::Signal)});
|
|
|
|
// A no-connect pin wired to a driver → nc-wired (and not undriven: the
|
|
// NC pin is Power-direction here, so the net has a driver).
|
|
wire(m, "NET_NC", {mkpin(u1, "NC1", PinDirection::Power, PinFunction::NoConnect),
|
|
mkpin(u2, "D1", PinDirection::Out, PinFunction::Signal)});
|
|
|
|
// Healthy: one driver + one input → no anomaly.
|
|
wire(m, "NET_OK", {mkpin(u1, "OD", PinDirection::Out, PinFunction::Signal),
|
|
mkpin(u2, "ID", PinDirection::In, PinFunction::Signal)});
|
|
|
|
auto anoms = check_pin_specs(&sys);
|
|
CHECK(count_kind(anoms, AnomalyKind::DriveContention) == 1);
|
|
CHECK(count_kind(anoms, AnomalyKind::UndrivenNet) == 1);
|
|
CHECK(count_kind(anoms, AnomalyKind::NcWired) == 1);
|
|
}
|
|
|
|
TEST_CASE("check_pin_specs stays silent on un-modelled nets") {
|
|
System sys;
|
|
Module *m = sys.modules()->merge("M");
|
|
Part *u1 = new Part("U1");
|
|
Part *u2 = new Part("U2");
|
|
m->add(u1);
|
|
m->add(u2);
|
|
|
|
// No specs set (direction Unknown / function Unknown) → no findings.
|
|
Pin *a = new Pin("A");
|
|
Pin *b = new Pin("B");
|
|
u1->add(a);
|
|
u2->add(b);
|
|
wire(m, "NET", {a, b});
|
|
|
|
auto anoms = check_pin_specs(&sys);
|
|
CHECK(anoms.empty());
|
|
}
|
|
|
|
namespace {
|
|
|
|
// Add the four mandatory TAP pins to a part and return it.
|
|
Part *mk_tap(Module *m, const char *name)
|
|
{
|
|
Part *p = new Part(name);
|
|
mkpin(p, "TDI", PinDirection::In, PinFunction::JtagTdi);
|
|
mkpin(p, "TDO", PinDirection::Out, PinFunction::JtagTdo);
|
|
mkpin(p, "TMS", PinDirection::In, PinFunction::JtagTms);
|
|
mkpin(p, "TCK", PinDirection::In, PinFunction::JtagTck);
|
|
m->add(p);
|
|
return p;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("check_jtag_chain accepts a healthy daisy chain") {
|
|
System sys;
|
|
Module *m = sys.modules()->merge("M");
|
|
Part *u1 = mk_tap(m, "U1");
|
|
Part *u2 = mk_tap(m, "U2");
|
|
Part *u3 = mk_tap(m, "U3");
|
|
|
|
wire(m, "TCK", {u1->get("TCK"), u2->get("TCK"), u3->get("TCK")});
|
|
wire(m, "TMS", {u1->get("TMS"), u2->get("TMS"), u3->get("TMS")});
|
|
wire(m, "TDI_HEAD", {u1->get("TDI")}); // from the controller
|
|
wire(m, "L1", {u1->get("TDO"), u2->get("TDI")}); // U1 → U2
|
|
wire(m, "L2", {u2->get("TDO"), u3->get("TDI")}); // U2 → U3
|
|
wire(m, "TDO_TAIL", {u3->get("TDO")}); // back to the controller
|
|
|
|
CHECK(check_jtag_chain(&sys).empty());
|
|
}
|
|
|
|
TEST_CASE("check_jtag_chain flags a broken chain and a split bus") {
|
|
System sys;
|
|
Module *m = sys.modules()->merge("M");
|
|
Part *u1 = mk_tap(m, "U1");
|
|
Part *u2 = mk_tap(m, "U2");
|
|
Part *u3 = mk_tap(m, "U3");
|
|
|
|
wire(m, "TCK", {u1->get("TCK"), u2->get("TCK"), u3->get("TCK")});
|
|
// U3's TMS is on its own net → not bridged to the TMS bus.
|
|
wire(m, "TMS", {u1->get("TMS"), u2->get("TMS")});
|
|
wire(m, "TMS_ALT", {u3->get("TMS")});
|
|
// Chain link U2 → U3 is missing: U2.TDO and U3.TDI dangle.
|
|
wire(m, "TDI_HEAD", {u1->get("TDI")});
|
|
wire(m, "L1", {u1->get("TDO"), u2->get("TDI")});
|
|
wire(m, "U2_TDO", {u2->get("TDO")});
|
|
wire(m, "U3_TDI", {u3->get("TDI")});
|
|
wire(m, "TDO_TAIL", {u3->get("TDO")});
|
|
|
|
auto a = check_jtag_chain(&sys);
|
|
CHECK(count_kind(a, AnomalyKind::JtagChainBreak) >= 1);
|
|
CHECK(count_kind(a, AnomalyKind::JtagBusUnbridged) >= 1);
|
|
}
|
|
|
|
TEST_CASE("check_jtag_chain reports an incomplete TAP") {
|
|
System sys;
|
|
Module *m = sys.modules()->merge("M");
|
|
Part *u1 = new Part("U1");
|
|
// only TDI + TDO, no TMS/TCK
|
|
mkpin(u1, "TDI", PinDirection::In, PinFunction::JtagTdi);
|
|
mkpin(u1, "TDO", PinDirection::Out, PinFunction::JtagTdo);
|
|
m->add(u1);
|
|
|
|
auto a = check_jtag_chain(&sys);
|
|
CHECK(count_kind(a, AnomalyKind::JtagTapIncomplete) == 1);
|
|
}
|
|
|
|
TEST_CASE("check_source_conflicts flags a BSDL rail left unconnected") {
|
|
System sys;
|
|
Module *m = sys.modules()->merge("M");
|
|
Part *u = new Part("U1");
|
|
m->add(u);
|
|
|
|
// A BSDL power pin with no signal → conflict (a rail floated in the netlist).
|
|
Pin *vcc = new Pin("VCC");
|
|
vcc->spec.function = PinFunction::Power;
|
|
vcc->spec.source = SpecSource::Bsdl;
|
|
u->add(vcc);
|
|
|
|
// A BSDL ground pin that IS connected → no conflict.
|
|
Pin *gnd = mkpin(u, "GND", PinDirection::Power, PinFunction::Ground);
|
|
wire(m, "GNDNET", {gnd, mkpin(u, "X", PinDirection::Out, PinFunction::Signal)});
|
|
|
|
auto a = check_source_conflicts(&sys);
|
|
CHECK(count_kind(a, AnomalyKind::SourceConflict) == 1);
|
|
}
|