Verify: check diff pairs/buses crossing connections (swap, incomplete)
New pass 8 (core/domain/diff_check.{hpp,cpp}): every complete local diff
pair (X_P/X_N, name-based) resolves its legs to two bridged nets; peer
pairs on those nets must match leg for leg.
- DiffPolaritySwap: P legs meet N legs across a connection (sometimes
intentional - reported for review), or both legs joined onto one net.
- DiffCrossIncomplete: pairs sharing only one leg, and diff-bus lanes
crossing NOWHERE while sibling lanes cross (distributed/fan-out buses
stay silent - validated against the real 7-card VPX system: 21 noisy
findings down to 3 genuine dangling-lane reports, 0 false swaps).
diff_suffix/split_trailing_index/is_internal_name promoted out of
analysis.cpp's anonymous namespace for reuse. VerifyReport.diff_anomalies
wired into model_total() and all four renderers (TUI verify, script
engine, wx log, analyze Issues). 8 new test cases (466 assertions).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
184
tests/test_diff_check.cpp
Normal file
184
tests/test_diff_check.cpp
Normal file
@@ -0,0 +1,184 @@
|
||||
#include <doctest/doctest.h>
|
||||
|
||||
#include "core/app/verify.hpp"
|
||||
#include "core/domain/analysis.hpp"
|
||||
#include "core/domain/connect.hpp"
|
||||
#include "core/domain/modules.hpp"
|
||||
#include "core/domain/parts.hpp"
|
||||
#include "core/domain/pins.hpp"
|
||||
#include "core/domain/signals.hpp"
|
||||
#include "core/domain/system.hpp"
|
||||
|
||||
// check_diff_crossings: a complete local diff pair (X_P / X_N) must cross a
|
||||
// connection leg for leg. Both sides are judged by name only.
|
||||
|
||||
namespace {
|
||||
|
||||
// New pin on `part`, wired to (or creating) module signal `sig_name`.
|
||||
Pin *wire(Module *m, Part *p, const std::string &pin_name,
|
||||
const std::string &sig_name)
|
||||
{
|
||||
Pin *pin = new Pin(pin_name);
|
||||
p->add(pin);
|
||||
Signal *s = m->signals->merge(sig_name);
|
||||
s->add(pin);
|
||||
pin->connect(s);
|
||||
return pin;
|
||||
}
|
||||
|
||||
struct Rig {
|
||||
System sys;
|
||||
Module *a, *b;
|
||||
Part *ja, *jb;
|
||||
Rig() {
|
||||
a = sys.modules()->merge("A");
|
||||
b = sys.modules()->merge("B");
|
||||
ja = new Part("J1"); a->add(ja);
|
||||
jb = new Part("P1"); b->add(jb);
|
||||
}
|
||||
void bridge(std::initializer_list<std::pair<Pin *, Pin *>> wires) {
|
||||
Connection *c = new Connection("A/J1 <-> B/P1", a, ja, b, jb);
|
||||
c->transform_name = "identity";
|
||||
for (const auto &w : wires) c->pin_map.push_back(w);
|
||||
sys.connections()->add(c);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("diff crossing: straight P↔P / N↔N is silent") {
|
||||
Rig r;
|
||||
Pin *ap = wire(r.a, r.ja, "1", "TX_P");
|
||||
Pin *an = wire(r.a, r.ja, "2", "TX_N");
|
||||
Pin *bp = wire(r.b, r.jb, "1", "RX_P");
|
||||
Pin *bn = wire(r.b, r.jb, "2", "RX_N");
|
||||
r.bridge({{ap, bp}, {an, bn}});
|
||||
|
||||
app::VerifyReport vr = app::verify(&r.sys);
|
||||
CHECK(vr.diff_anomalies.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("diff crossing: swapped legs report ONE polarity-swap anomaly") {
|
||||
Rig r;
|
||||
Pin *ap = wire(r.a, r.ja, "1", "TX_P");
|
||||
Pin *an = wire(r.a, r.ja, "2", "TX_N");
|
||||
Pin *bp = wire(r.b, r.jb, "1", "RX_P");
|
||||
Pin *bn = wire(r.b, r.jb, "2", "RX_N");
|
||||
r.bridge({{ap, bn}, {an, bp}}); // crossed on purpose
|
||||
|
||||
app::VerifyReport vr = app::verify(&r.sys);
|
||||
REQUIRE(vr.diff_anomalies.size() == 1); // deduped: not once per side
|
||||
CHECK(vr.diff_anomalies[0].kind == AnomalyKind::DiffPolaritySwap);
|
||||
CHECK(vr.diff_anomalies[0].message.find("polarity swapped")
|
||||
!= std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("diff crossing: a single bridged leg reports incomplete") {
|
||||
Rig r;
|
||||
Pin *ap = wire(r.a, r.ja, "1", "TX_P");
|
||||
wire(r.a, r.ja, "2", "TX_N"); // N leg stays local
|
||||
Pin *bp = wire(r.b, r.jb, "1", "RX_P");
|
||||
wire(r.b, r.jb, "2", "RX_N"); // peer N leg exists, unbridged
|
||||
r.bridge({{ap, bp}});
|
||||
|
||||
app::VerifyReport vr = app::verify(&r.sys);
|
||||
REQUIRE(vr.diff_anomalies.size() == 1);
|
||||
CHECK(vr.diff_anomalies[0].kind == AnomalyKind::DiffCrossIncomplete);
|
||||
CHECK(vr.diff_anomalies[0].message.find("only the P legs")
|
||||
!= std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("diff crossing: an unsuffixed peer is not judged") {
|
||||
Rig r;
|
||||
Pin *ap = wire(r.a, r.ja, "1", "TX_P");
|
||||
Pin *an = wire(r.a, r.ja, "2", "TX_N");
|
||||
Pin *b1 = wire(r.b, r.jb, "1", "RXP"); // no _P/_N suffix
|
||||
Pin *b2 = wire(r.b, r.jb, "2", "RXN");
|
||||
r.bridge({{ap, b2}, {an, b1}}); // even crossed: silent
|
||||
|
||||
app::VerifyReport vr = app::verify(&r.sys);
|
||||
CHECK(vr.diff_anomalies.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("diff crossing: P and N joined onto one net is flagged") {
|
||||
Rig r;
|
||||
Pin *ap = wire(r.a, r.ja, "1", "TX_P");
|
||||
Pin *an = wire(r.a, r.ja, "2", "TX_N");
|
||||
Pin *b1 = wire(r.b, r.jb, "1", "X");
|
||||
Pin *b2 = wire(r.b, r.jb, "2", "X"); // same peer signal
|
||||
r.bridge({{ap, b1}, {an, b2}});
|
||||
|
||||
app::VerifyReport vr = app::verify(&r.sys);
|
||||
REQUIRE(vr.diff_anomalies.size() == 1);
|
||||
CHECK(vr.diff_anomalies[0].kind == AnomalyKind::DiffPolaritySwap);
|
||||
CHECK(vr.diff_anomalies[0].message.find("join the same net")
|
||||
!= std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("diff bus crossing: a dangling lane is listed, once per side") {
|
||||
Rig r;
|
||||
// Two lanes on each side; only lane 0 is bridged — lane 1 crosses nowhere.
|
||||
Pin *a0p = wire(r.a, r.ja, "1", "TX0_P");
|
||||
Pin *a0n = wire(r.a, r.ja, "2", "TX0_N");
|
||||
wire(r.a, r.ja, "3", "TX1_P");
|
||||
wire(r.a, r.ja, "4", "TX1_N");
|
||||
Pin *b0p = wire(r.b, r.jb, "1", "RX0_P");
|
||||
Pin *b0n = wire(r.b, r.jb, "2", "RX0_N");
|
||||
wire(r.b, r.jb, "3", "RX1_P");
|
||||
wire(r.b, r.jb, "4", "RX1_N");
|
||||
r.bridge({{a0p, b0p}, {a0n, b0n}});
|
||||
|
||||
app::VerifyReport vr = app::verify(&r.sys);
|
||||
// One aggregated anomaly per side (each names its own lane signals).
|
||||
REQUIRE(vr.diff_anomalies.size() == 2);
|
||||
for (const auto &an : vr.diff_anomalies) {
|
||||
CHECK(an.kind == AnomalyKind::DiffCrossIncomplete);
|
||||
CHECK(an.message.find("lane(s) 1 do not cross") != std::string::npos);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("diff bus crossing: a distributed bus (lanes fanned out) is silent") {
|
||||
// A's two lanes go to two DIFFERENT modules — legitimate backplane fan-out.
|
||||
System sys;
|
||||
Module *a = sys.modules()->merge("A");
|
||||
Module *b = sys.modules()->merge("B");
|
||||
Module *c = sys.modules()->merge("C");
|
||||
Part *ja = new Part("J1"); a->add(ja);
|
||||
Part *jb = new Part("P1"); b->add(jb);
|
||||
Part *jc = new Part("P1"); c->add(jc);
|
||||
Pin *a0p = wire(a, ja, "1", "TX0_P");
|
||||
Pin *a0n = wire(a, ja, "2", "TX0_N");
|
||||
Pin *a1p = wire(a, ja, "3", "TX1_P");
|
||||
Pin *a1n = wire(a, ja, "4", "TX1_N");
|
||||
Pin *b0p = wire(b, jb, "1", "RX0_P");
|
||||
Pin *b0n = wire(b, jb, "2", "RX0_N");
|
||||
Pin *c0p = wire(c, jc, "1", "RX0_P");
|
||||
Pin *c0n = wire(c, jc, "2", "RX0_N");
|
||||
|
||||
Connection *cb = new Connection("A/J1 <-> B/P1", a, ja, b, jb);
|
||||
cb->transform_name = "identity";
|
||||
cb->pin_map.emplace_back(a0p, b0p);
|
||||
cb->pin_map.emplace_back(a0n, b0n);
|
||||
sys.connections()->add(cb);
|
||||
Connection *cc = new Connection("A/J1 <-> C/P1", a, ja, c, jc);
|
||||
cc->transform_name = "identity";
|
||||
cc->pin_map.emplace_back(a1p, c0p);
|
||||
cc->pin_map.emplace_back(a1n, c0n);
|
||||
sys.connections()->add(cc);
|
||||
|
||||
app::VerifyReport vr = app::verify(&sys);
|
||||
CHECK(vr.diff_anomalies.empty()); // every lane crosses somewhere
|
||||
}
|
||||
|
||||
TEST_CASE("diff crossing: empty / unconnected systems are silent") {
|
||||
System sys;
|
||||
app::VerifyReport vr = app::verify(&sys);
|
||||
CHECK(vr.diff_anomalies.empty());
|
||||
|
||||
// A pair that never crosses anything: silent (local pairs are fine).
|
||||
Rig r;
|
||||
wire(r.a, r.ja, "1", "TX_P");
|
||||
wire(r.a, r.ja, "2", "TX_N");
|
||||
vr = app::verify(&r.sys);
|
||||
CHECK(vr.diff_anomalies.empty());
|
||||
}
|
||||
Reference in New Issue
Block a user