Signal analysis pass (analyze), NC tests, DESIGN.md catch-up.

- New `src/system/analysis.{hpp,cpp}` — stateless post-processing pass
  `analyze_system(System*) → AnalysisReport`. Per-module detection of
  signal groups and anomalies; pure read, re-runnable.
  - Groups: diff pairs (`*_P` / `*_N`, case-insensitive), buses
    (`NAME[N]` or strict `NAME_N` — the `_` before digits is required
    so names like `GETH_01_VDD12` are not misread as a bus).
  - Anomalies: `DiffPairOrphan` (asymmetric: only `_P` without `_N` is
    reported — `_N` alone is overloaded with active-low semantics and
    floods the output with false positives), `BusGap` (missing index
    inside a detected `[lo..hi]`).
  - Noise filters: signals starting with `$` (Mentor internals) are
    skipped wholesale.
- New `analyze` shell command — prints groups sorted by module +
  label, then anomalies. Sized for the upcoming dashboard.
- `tests/test_analysis.cpp` — 8 cases covering both detectors, false-
  positive guards (no-underscore digits, `$`-prefixed internals), and
  per-module scoping.
- `tests/test_nc_origin.cpp` — completes the prior NC-tagging commit
  with round-trip + drop_singleton_signals coverage.
- DESIGN.md updated: layout entry for `analysis.{hpp,cpp}` and new
  section explaining the pass; NC-origin paragraph aligned with the
  actual tag semantics and the verify three-pass summary.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-14 13:42:58 +02:00
parent 280526304d
commit 5e89b33088
6 changed files with 598 additions and 3 deletions

View File

@@ -1,6 +1,7 @@
#include "tui/tui.hpp"
#include "tui/tui_helpers.hpp"
#include "system/analysis.hpp"
#include "system/connect.hpp"
#include "system/modules.hpp"
#include "system/nets.hpp"
@@ -280,6 +281,56 @@ void Tui::RegisterCommands() {
}, true,
"check pin roles locally and signal-type consistency across bridged nets" };
commands["analyze"] = { {}, [this](auto &) {
if (!sys) { Print("no system: run 'new' first."); return; }
AnalysisReport rep = analyze_system(sys.get());
int n_diff = 0, n_bus = 0;
for (const auto &g : rep.groups) {
if (g.kind == GroupKind::DiffPair) ++n_diff;
else if (g.kind == GroupKind::Bus) ++n_bus;
}
int n_dp_orph = 0, n_bus_gap = 0;
for (const auto &a : rep.anomalies) {
if (a.kind == AnomalyKind::DiffPairOrphan) ++n_dp_orph;
else if (a.kind == AnomalyKind::BusGap) ++n_bus_gap;
}
Print("analyze: " + std::to_string(n_diff) + " diff pair(s), "
+ std::to_string(n_bus) + " bus(es).");
// Sort groups by module then label so output is stable.
auto by_label = [](const SignalGroup &a, const SignalGroup &b) {
std::string ma = a.module ? a.module->name : std::string{};
std::string mb = b.module ? b.module->name : std::string{};
if (ma != mb) return ma < mb;
return a.label < b.label;
};
auto groups = rep.groups; // copy: report stays untouched
std::sort(groups.begin(), groups.end(), by_label);
for (const auto &g : groups) {
std::string mname = g.module ? g.module->name : std::string("?");
std::string line = " " + mname + "/" + g.label
+ " [" + group_kind_name(g.kind) + "]"
+ "" + std::to_string(g.members.size())
+ " signal(s)";
Print(line);
}
if (rep.anomalies.empty()) {
Print("analyze: no anomaly.");
} else {
Print("analyze: " + std::to_string(rep.anomalies.size())
+ " anomaly(ies) ("
+ std::to_string(n_dp_orph) + " diff-pair orphan, "
+ std::to_string(n_bus_gap) + " bus gap):");
for (const auto &a : rep.anomalies)
Print(" [" + std::string(anomaly_kind_name(a.kind)) + "] "
+ a.message);
}
}, true,
"detect signal groups (diff pairs, buses) and structural anomalies" };
commands["net"] = {
{{"module", Completion::None},
{"signal name", Completion::None}},