verify: JTAG boundary-scan chain integrity (flagship)

New check_jtag_chain(System*): collects TAP pins by PinSpec.function, resolves
each to its net, and flags JtagTapIncomplete (a device missing TDI/TDO/TMS/
TCK), JtagBusUnbridged (TMS or TCK not common to every TAP device), and
JtagChainBreak (dangling TDO/TDI, chain fan-out, or not a single head->tail
daisy chain). Surfaced as a pass in `verify`; AnomalyKind extended. Covered by
test_bsdl_check (healthy chain, broken chain + split bus, incomplete TAP).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-03 15:24:00 +02:00
parent 5caa4c530d
commit 952afe3979
6 changed files with 256 additions and 0 deletions

View File

@@ -90,3 +90,72 @@ TEST_CASE("check_pin_specs stays silent on un-modelled nets") {
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);
}