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:
@@ -26,6 +26,9 @@ const char *anomaly_kind_name(AnomalyKind k) {
|
|||||||
case AnomalyKind::DriveContention: return "drive-contention";
|
case AnomalyKind::DriveContention: return "drive-contention";
|
||||||
case AnomalyKind::UndrivenNet: return "undriven-net";
|
case AnomalyKind::UndrivenNet: return "undriven-net";
|
||||||
case AnomalyKind::NcWired: return "nc-wired";
|
case AnomalyKind::NcWired: return "nc-wired";
|
||||||
|
case AnomalyKind::JtagTapIncomplete: return "jtag-tap-incomplete";
|
||||||
|
case AnomalyKind::JtagChainBreak: return "jtag-chain-break";
|
||||||
|
case AnomalyKind::JtagBusUnbridged: return "jtag-bus-unbridged";
|
||||||
}
|
}
|
||||||
return "?";
|
return "?";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,9 @@ enum class AnomalyKind {
|
|||||||
DriveContention, ///< A net has ≥2 push-pull output (Out) drivers.
|
DriveContention, ///< A net has ≥2 push-pull output (Out) drivers.
|
||||||
UndrivenNet, ///< A net has input(s) but no driver (Out/Bidir/Power).
|
UndrivenNet, ///< A net has input(s) but no driver (Out/Bidir/Power).
|
||||||
NcWired, ///< A no-connect pin is wired onto a multi-pin net.
|
NcWired, ///< A no-connect pin is wired onto a multi-pin net.
|
||||||
|
JtagTapIncomplete, ///< A TAP device is missing one of TDI/TDO/TMS/TCK.
|
||||||
|
JtagChainBreak, ///< The TDO→TDI daisy chain is broken / not a single path.
|
||||||
|
JtagBusUnbridged, ///< TMS or TCK is not common to all TAP devices.
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Anomaly {
|
struct Anomaly {
|
||||||
|
|||||||
@@ -9,10 +9,18 @@
|
|||||||
#include "system.hpp"
|
#include "system.hpp"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
std::string part_label(const Part *p)
|
||||||
|
{
|
||||||
|
std::string mod = (p && p->prnt) ? p->prnt->name : std::string("?");
|
||||||
|
return mod + "/" + (p ? p->name : std::string("?"));
|
||||||
|
}
|
||||||
|
|
||||||
std::string pin_label(const Pin *p)
|
std::string pin_label(const Pin *p)
|
||||||
{
|
{
|
||||||
std::string part = (p && p->prnt) ? p->prnt->name : std::string("?");
|
std::string part = (p && p->prnt) ? p->prnt->name : std::string("?");
|
||||||
@@ -109,3 +117,160 @@ std::vector<Anomaly> check_pin_specs(System *sys)
|
|||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<Anomaly> check_jtag_chain(System *sys)
|
||||||
|
{
|
||||||
|
std::vector<Anomaly> out;
|
||||||
|
if (!sys)
|
||||||
|
return out;
|
||||||
|
|
||||||
|
// Map every pin to the index of the net it sits on.
|
||||||
|
std::vector<Net> nets = compute_all_nets(sys);
|
||||||
|
std::unordered_map<Pin *, int> net_of;
|
||||||
|
for (size_t i = 0; i < nets.size(); ++i)
|
||||||
|
for (auto &mp : nets[i].members)
|
||||||
|
for (auto &kv : *mp.second)
|
||||||
|
net_of[kv.second] = (int)i;
|
||||||
|
auto net_id = [&](Pin *p) -> int {
|
||||||
|
if (!p) return -1;
|
||||||
|
auto it = net_of.find(p);
|
||||||
|
return it == net_of.end() ? -1 : it->second;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Collect TAP devices: any part carrying ≥1 pin with a TAP function.
|
||||||
|
struct Tap {
|
||||||
|
Part *part = nullptr;
|
||||||
|
Pin *tdi = nullptr, *tdo = nullptr, *tms = nullptr, *tck = nullptr, *trst = nullptr;
|
||||||
|
};
|
||||||
|
std::vector<Tap> taps;
|
||||||
|
for (auto &mkv : *sys->modules())
|
||||||
|
for (auto &pkv : *mkv.second) {
|
||||||
|
Tap t;
|
||||||
|
t.part = pkv.second;
|
||||||
|
bool any = false;
|
||||||
|
for (auto &nkv : *t.part) {
|
||||||
|
Pin *pin = nkv.second;
|
||||||
|
switch (pin->spec.function) {
|
||||||
|
case PinFunction::JtagTdi: t.tdi = pin; any = true; break;
|
||||||
|
case PinFunction::JtagTdo: t.tdo = pin; any = true; break;
|
||||||
|
case PinFunction::JtagTms: t.tms = pin; any = true; break;
|
||||||
|
case PinFunction::JtagTck: t.tck = pin; any = true; break;
|
||||||
|
case PinFunction::JtagTrst: t.trst = pin; any = true; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (any) taps.push_back(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (taps.empty())
|
||||||
|
return out;
|
||||||
|
|
||||||
|
// (1) every TAP device needs the four mandatory signals.
|
||||||
|
for (const Tap &t : taps) {
|
||||||
|
std::string miss;
|
||||||
|
auto need = [&](Pin *p, const char *n) {
|
||||||
|
if (!p) miss += (miss.empty() ? "" : ", ") + std::string(n);
|
||||||
|
};
|
||||||
|
need(t.tdi, "TDI"); need(t.tdo, "TDO"); need(t.tms, "TMS"); need(t.tck, "TCK");
|
||||||
|
if (!miss.empty()) {
|
||||||
|
Anomaly a;
|
||||||
|
a.kind = AnomalyKind::JtagTapIncomplete;
|
||||||
|
a.module = t.part ? t.part->prnt : nullptr;
|
||||||
|
a.message = part_label(t.part) + ": incomplete TAP, missing " + miss;
|
||||||
|
out.push_back(std::move(a));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// (2) TMS and TCK must each be one common net across all TAP devices.
|
||||||
|
auto check_bus = [&](const char *name, const std::vector<std::pair<Part *, Pin *>> &pins) {
|
||||||
|
if (pins.size() < 2)
|
||||||
|
return;
|
||||||
|
int ref = -1;
|
||||||
|
for (const auto &pp : pins) {
|
||||||
|
int n = net_id(pp.second);
|
||||||
|
if (n >= 0) { ref = n; break; }
|
||||||
|
}
|
||||||
|
if (ref < 0) {
|
||||||
|
Anomaly a;
|
||||||
|
a.kind = AnomalyKind::JtagBusUnbridged;
|
||||||
|
a.message = std::string(name) + ": no TAP device has a connected " + name;
|
||||||
|
out.push_back(std::move(a));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::string off;
|
||||||
|
for (const auto &pp : pins)
|
||||||
|
if (net_id(pp.second) != ref)
|
||||||
|
off += (off.empty() ? "" : ", ") + part_label(pp.first);
|
||||||
|
if (!off.empty()) {
|
||||||
|
Anomaly a;
|
||||||
|
a.kind = AnomalyKind::JtagBusUnbridged;
|
||||||
|
a.message = std::string(name) + " is not common to all TAP devices (off-bus: "
|
||||||
|
+ off + ")";
|
||||||
|
out.push_back(std::move(a));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
std::vector<std::pair<Part *, Pin *>> tms_pins, tck_pins;
|
||||||
|
for (const Tap &t : taps) {
|
||||||
|
if (t.tms) tms_pins.push_back({t.part, t.tms});
|
||||||
|
if (t.tck) tck_pins.push_back({t.part, t.tck});
|
||||||
|
}
|
||||||
|
check_bus("TMS", tms_pins);
|
||||||
|
check_bus("TCK", tck_pins);
|
||||||
|
|
||||||
|
// (3) TDO→TDI daisy chain: a link is two devices sharing a net (A.tdo, B.tdi).
|
||||||
|
const int n = (int)taps.size();
|
||||||
|
std::vector<int> downstream(n, 0), upstream(n, 0);
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
if (taps[i].tdo && net_id(taps[i].tdo) < 0) {
|
||||||
|
Anomaly a;
|
||||||
|
a.kind = AnomalyKind::JtagChainBreak;
|
||||||
|
a.module = taps[i].part ? taps[i].part->prnt : nullptr;
|
||||||
|
a.message = part_label(taps[i].part) + "/TDO is not connected to any net";
|
||||||
|
out.push_back(std::move(a));
|
||||||
|
}
|
||||||
|
if (taps[i].tdi && net_id(taps[i].tdi) < 0) {
|
||||||
|
Anomaly a;
|
||||||
|
a.kind = AnomalyKind::JtagChainBreak;
|
||||||
|
a.module = taps[i].part ? taps[i].part->prnt : nullptr;
|
||||||
|
a.message = part_label(taps[i].part) + "/TDI is not connected to any net";
|
||||||
|
out.push_back(std::move(a));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < n; ++i)
|
||||||
|
for (int j = 0; j < n; ++j) {
|
||||||
|
if (i == j) continue;
|
||||||
|
int a = net_id(taps[i].tdo), b = net_id(taps[j].tdi);
|
||||||
|
if (a >= 0 && a == b) { ++downstream[i]; ++upstream[j]; }
|
||||||
|
}
|
||||||
|
int heads = 0, tails = 0;
|
||||||
|
for (int i = 0; i < n; ++i) {
|
||||||
|
if (downstream[i] > 1) {
|
||||||
|
Anomaly a;
|
||||||
|
a.kind = AnomalyKind::JtagChainBreak;
|
||||||
|
a.module = taps[i].part ? taps[i].part->prnt : nullptr;
|
||||||
|
a.message = part_label(taps[i].part) + "/TDO drives "
|
||||||
|
+ std::to_string(downstream[i]) + " TDIs (chain fan-out)";
|
||||||
|
out.push_back(std::move(a));
|
||||||
|
}
|
||||||
|
if (upstream[i] > 1) {
|
||||||
|
Anomaly a;
|
||||||
|
a.kind = AnomalyKind::JtagChainBreak;
|
||||||
|
a.module = taps[i].part ? taps[i].part->prnt : nullptr;
|
||||||
|
a.message = part_label(taps[i].part) + "/TDI is driven by "
|
||||||
|
+ std::to_string(upstream[i]) + " TDOs";
|
||||||
|
out.push_back(std::move(a));
|
||||||
|
}
|
||||||
|
if (taps[i].tdi && net_id(taps[i].tdi) >= 0 && upstream[i] == 0) ++heads;
|
||||||
|
if (taps[i].tdo && net_id(taps[i].tdo) >= 0 && downstream[i] == 0) ++tails;
|
||||||
|
}
|
||||||
|
if (n > 1 && (heads != 1 || tails != 1)) {
|
||||||
|
Anomaly a;
|
||||||
|
a.kind = AnomalyKind::JtagChainBreak;
|
||||||
|
a.message = "JTAG chain is not a single daisy chain: " + std::to_string(heads)
|
||||||
|
+ " head(s), " + std::to_string(tails) + " tail(s) over "
|
||||||
|
+ std::to_string(n) + " TAP device(s)";
|
||||||
|
out.push_back(std::move(a));
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,4 +16,13 @@ class System;
|
|||||||
// un-modelled parts).
|
// un-modelled parts).
|
||||||
std::vector<Anomaly> check_pin_specs(System *sys);
|
std::vector<Anomaly> check_pin_specs(System *sys);
|
||||||
|
|
||||||
|
// JTAG boundary-scan chain integrity, using pins whose PinSpec.function is a TAP
|
||||||
|
// role (JtagTdi/Tdo/Tms/Tck/Trst). Resolves each TAP pin to its net and checks:
|
||||||
|
// - JtagTapIncomplete : a TAP device missing TDI/TDO/TMS/TCK;
|
||||||
|
// - JtagBusUnbridged : TMS or TCK not common to every TAP device;
|
||||||
|
// - JtagChainBreak : the TDO→TDI daisy chain dangles, fans out, or is not a
|
||||||
|
// single path (≠1 head / ≠1 tail).
|
||||||
|
// Empty when the system has no TAP pins.
|
||||||
|
std::vector<Anomaly> check_jtag_chain(System *sys);
|
||||||
|
|
||||||
#endif // _BSDL_CHECK_HPP_
|
#endif // _BSDL_CHECK_HPP_
|
||||||
|
|||||||
@@ -304,6 +304,13 @@ void Tui::RegisterCommands() {
|
|||||||
Print(" [" + std::string(anomaly_kind_name(a.kind)) + "] " + a.message);
|
Print(" [" + std::string(anomaly_kind_name(a.kind)) + "] " + a.message);
|
||||||
Print("verify: " + std::to_string(pin_anoms.size())
|
Print("verify: " + std::to_string(pin_anoms.size())
|
||||||
+ " model-driven pin anomaly(ies).");
|
+ " model-driven pin anomaly(ies).");
|
||||||
|
|
||||||
|
// JTAG boundary-scan chain integrity (TAP pins → nets).
|
||||||
|
auto jtag_anoms = check_jtag_chain(sys.get());
|
||||||
|
for (const auto &a : jtag_anoms)
|
||||||
|
Print(" [" + std::string(anomaly_kind_name(a.kind)) + "] " + a.message);
|
||||||
|
Print("verify: " + std::to_string(jtag_anoms.size())
|
||||||
|
+ " JTAG chain anomaly(ies).");
|
||||||
}, true,
|
}, true,
|
||||||
"check pin roles, bridged-net consistency, and model-driven pin specs (contention/undriven/NC)" };
|
"check pin roles, bridged-net consistency, and model-driven pin specs (contention/undriven/NC)" };
|
||||||
|
|
||||||
|
|||||||
@@ -90,3 +90,72 @@ TEST_CASE("check_pin_specs stays silent on un-modelled nets") {
|
|||||||
auto anoms = check_pin_specs(&sys);
|
auto anoms = check_pin_specs(&sys);
|
||||||
CHECK(anoms.empty());
|
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);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user