diff --git a/DESIGN.md b/DESIGN.md index aef6df2..60064f4 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -167,7 +167,7 @@ Exposed as the `analyze` shell command which prints groups (sorted by module + l **Analyze screen** (`screen_analyze.cpp`, dashboard shortcut `a`, `screen_idx = 7`): unified **verify + analyze** view with a tabbed layout — horizontal tab bar at the top (`Issues (…) │ Groups (…) │ Types: …`), and a single scrollable detail panel below showing the active tab's list. Tab swap is handled at the outer `CatchEvent` (`Tab` / `→` cycle forward, `Shift-Tab` / `←` cycle back). The detail uses `Container::Tab({issues_menu, groups_menu, types_menu}, &analyze_focus_idx)` so `↑/↓` always navigate the visible list; each tab preserves its own selection idx. -- **Issues** pane merges: pin-role mismatches (typed pins whose actual signal type disagrees with the role from `connector_type`), bridged-net Power↔Gnd inconsistencies (the BFS check formerly in `verify` pass 2), and the structural anomalies from `analyze_system` (`DiffPairOrphan`, `BusGap`, `DiffBusGap`). Header counts each category. +- **Issues** pane merges: pin-role mismatches (typed pins whose actual signal type disagrees with the role from `connector_type`), bridged-net Power↔Gnd inconsistencies (the BFS check formerly in `verify` pass 2), the structural anomalies from `analyze_system` (`DiffPairOrphan`, `BusGap`, `DiffBusGap`), and the model-driven checks (`check_pin_specs` / `check_jtag_chain` / `check_source_conflicts`, tagged `model` in the header). Header counts each category. - **Groups** pane lists every detected `SignalGroup` sorted by `module / label` with kind tag and member count. - **Types** pane lists per-signal Power decisions (`[Power confirmed]` / `[Power REFUTED]` / `[Gnd]`) plus a trailing `[NC]` orphan rollup line. The pane header summarises counts (`N pwr-ok, M refuted, K gnd`). @@ -197,7 +197,7 @@ Today the only caller is `export` (`{"CSV", ".csv"}, {"ODS", ".ods"}` filters, k **Command palette** (`screen_palette.cpp`): a global modal launcher attached to the whole tab tree via `tab | Modal(BuildPaletteModal(), &palette_open)` in `Run()`. Trigger: `Event::CtrlP` (FTXUI Input does not consume Ctrl-P, so the outer `CatchEvent` reliably picks it up first). Behaviour: a single Input bound to `palette_query` plus a result list rebuilt on every frame. Indexes three kinds of entries: commands (from the `commands` map), modules and per-module signals (qualified as `module/signal`). Fuzzy match is subsequence-based, case-insensitive: lower score wins, computed as `first_match_position * 100 + sum_of_gaps`. Kinds are biased by a constant offset (commands +0, modules +1000, signals +2000) so command matches come first when scores tie. Output capped at 20 rows to keep render cheap on big systems. Activation (`Enter`): commands → `Dispatch(name)` (which dispatches like the shell, including opening interactive screens), module → prefill `explore_*` state and jump to `screen_idx = 4`, signal → prefill `net_modules` + seed `net_sig_filter` to the exact signal name and jump to `screen_idx = 5`. `Esc` closes the palette. While the palette is open, the outer `CatchEvent` cedes events to it so Tab/Esc/etc. don't leak into the underlying screen. -**Dashboard** (`screen_dashboard.cpp`, `dashboard` command, `screen_idx = 4`): read-only system overview. Single Renderer, no Input child. Recomputes everything per frame (cheap on realistic sizes): counters (modules/parts/signals/connections), three health rows (verify pin-role mismatches, bridged-net inconsistencies, NC orphans — green check / yellow warning prefix), an analysis summary line (diff pairs / buses / anomaly count, coloured if non-zero), and a per-module table (parts / signals / `connector_type`-tagged parts). Letter shortcuts handled in the outer `CatchEvent`: `c`=console, `p`=plug (connect), `t`=set-connector-type, `e`=explore, `a`=analyze, `q`=quit. `Esc` is swallowed on the dashboard (home). The dashboard is `interactive = true`, `scriptable = false`; running `dashboard` inside `source` aborts the script. +**Dashboard** (`screen_dashboard.cpp`, `dashboard` command, `screen_idx = 4`): read-only system overview. Single Renderer, no Input child. Recomputes everything per frame (cheap on realistic sizes): counters (modules/parts/signals/connections), four health rows (verify pin-role mismatches, bridged-net inconsistencies, NC orphans, and BSDL/JTAG model anomalies — green check / yellow warning prefix), an analysis summary line (diff pairs / buses / anomaly count, coloured if non-zero), and a per-module table (parts / signals / `connector_type`-tagged parts). Letter shortcuts handled in the outer `CatchEvent`: `c`=console, `p`=plug (connect), `t`=set-connector-type, `e`=explore, `a`=analyze, `q`=quit. `Esc` is swallowed on the dashboard (home). The dashboard is `interactive = true`, `scriptable = false`; running `dashboard` inside `source` aborts the script. **Screen titles** (shared idiom): every interactive screen renders a top bar in the form `" essim "` (bold) + `"→ "` (dim) + `""` (bold) + `" — "` (dim), followed by a `separator()`. The main screen has its own variant that adds a live `N module(s), M connection(s)` counter on the right. Aim is to make the breadcrumb between essim and the current mode visible at all times. @@ -299,7 +299,7 @@ The analyze screen additionally surfaces two "verify-class" issues, computed the - **pin-role mismatch** — a pin whose `expected_signal_type()` (derived from its `PinSpec`, set by `set-connector-type` via `pin_role(connector_type, pin_name)`) disagrees with the actual signal type. - **net-mix** — a bridged net (BFS over `Connection::pin_map`, ≥ 2 members) where `net_type_consistent(net, &dominant)` returns false. Specifically, the net contains both `Power` and `GndShield` signals. -The `verify` command (not the analyze screen, yet) also emits the **model-driven `AnomalyKind`s** from `bsdl_check.{hpp,cpp}`: `DriveContention` / `UndrivenNet` / `NcWired` (`check_pin_specs`) and `JtagTapIncomplete` / `JtagChainBreak` / `JtagBusUnbridged` (`check_jtag_chain`); and `SourceConflict` (`check_source_conflicts` — a BSDL power/ground pin the netlist leaves unconnected). They consume the BSDL-populated `PinSpec` plus `compute_all_nets`. Surfacing them in the analyze/dashboard Issues pane is a TODO. +The `verify` command (not the analyze screen, yet) also emits the **model-driven `AnomalyKind`s** from `bsdl_check.{hpp,cpp}`: `DriveContention` / `UndrivenNet` / `NcWired` (`check_pin_specs`) and `JtagTapIncomplete` / `JtagChainBreak` / `JtagBusUnbridged` (`check_jtag_chain`); and `SourceConflict` (`check_source_conflicts` — a BSDL power/ground pin the netlist leaves unconnected). They consume the BSDL-populated `PinSpec` plus `compute_all_nets`, and are surfaced in three places: the `verify` command, the analyze screen's Issues pane (counted as `… N model`), and a `model:` health row on the dashboard. `check_pin_specs`/`check_jtag_chain` accept an optional precomputed net list, so verify, analyze and the dashboard each compute the nets once and reuse them across checks. ### Component kind diff --git a/src/system/bsdl_check.cpp b/src/system/bsdl_check.cpp index 93b6117..e200c41 100644 --- a/src/system/bsdl_check.cpp +++ b/src/system/bsdl_check.cpp @@ -39,13 +39,16 @@ std::string join_labels(const std::vector &pins) } // namespace -std::vector check_pin_specs(System *sys) +std::vector check_pin_specs(System *sys, const std::vector *nets) { std::vector out; if (!sys) return out; - for (const Net &net : compute_all_nets(sys)) { + std::vector local; + if (!nets) + local = compute_all_nets(sys); + for (const Net &net : (nets ? *nets : local)) { std::vector pins; std::vector sigs; Module *mod = nullptr; @@ -123,14 +126,17 @@ std::vector check_pin_specs(System *sys) return out; } -std::vector check_jtag_chain(System *sys) +std::vector check_jtag_chain(System *sys, const std::vector *nets_in) { std::vector out; if (!sys) return out; // Map every pin to the index of the net it sits on. - std::vector nets = compute_all_nets(sys); + std::vector local; + if (!nets_in) + local = compute_all_nets(sys); + const std::vector &nets = nets_in ? *nets_in : local; std::unordered_map net_of; for (size_t i = 0; i < nets.size(); ++i) for (auto &mp : nets[i].members) diff --git a/src/system/bsdl_check.hpp b/src/system/bsdl_check.hpp index 65af80a..dbc95b2 100644 --- a/src/system/bsdl_check.hpp +++ b/src/system/bsdl_check.hpp @@ -2,11 +2,16 @@ #define _BSDL_CHECK_HPP_ #include "analysis.hpp" // Anomaly, AnomalyKind +#include "nets.hpp" // Net #include class System; +// The net checks below accept an optional precomputed net list: callers that +// already have one (verify, the analyze screen, the dashboard) pass it so the +// transitive-closure pass isn't redone. Pass nullptr to compute it internally. + // Model-driven pin checks over the system's nets, using the PinSpec // direction/function populated by connector or BSDL models. Emits: // - DriveContention : a net with ≥2 push-pull output drivers; @@ -14,7 +19,7 @@ class System; // - NcWired : a no-connect pin wired onto a multi-pin net. // Read-only; nets with no direction data are skipped (no false positives on // un-modelled parts). -std::vector check_pin_specs(System *sys); +std::vector check_pin_specs(System *sys, const std::vector *nets = nullptr); // 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: @@ -23,7 +28,7 @@ std::vector check_pin_specs(System *sys); // - 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 check_jtag_chain(System *sys); +std::vector check_jtag_chain(System *sys, const std::vector *nets = nullptr); // Conflicts between a device model and the netlist's own view of a pin. Today: // a pin the BSDL declares power/ground (a must-connect rail) that the netlist diff --git a/src/tui/commands.cpp b/src/tui/commands.cpp index 459a1ea..fd20707 100644 --- a/src/tui/commands.cpp +++ b/src/tui/commands.cpp @@ -300,14 +300,14 @@ void Tui::RegisterCommands() { // Model-driven pin checks (drive contention / undriven net / NC-wired) // from the PinSpec direction/function populated by connector/BSDL models. - auto pin_anoms = check_pin_specs(sys.get()); + auto pin_anoms = check_pin_specs(sys.get(), &nets); for (const auto &a : pin_anoms) Print(" [" + std::string(anomaly_kind_name(a.kind)) + "] " + a.message); Print("verify: " + std::to_string(pin_anoms.size()) + " model-driven pin anomaly(ies)."); // JTAG boundary-scan chain integrity (TAP pins → nets). - auto jtag_anoms = check_jtag_chain(sys.get()); + auto jtag_anoms = check_jtag_chain(sys.get(), &nets); for (const auto &a : jtag_anoms) Print(" [" + std::string(anomaly_kind_name(a.kind)) + "] " + a.message); Print("verify: " + std::to_string(jtag_anoms.size()) diff --git a/src/tui/screen_analyze.cpp b/src/tui/screen_analyze.cpp index d3020dd..f03c92b 100644 --- a/src/tui/screen_analyze.cpp +++ b/src/tui/screen_analyze.cpp @@ -2,6 +2,7 @@ #include "tui/tui_helpers.hpp" #include "system/analysis.hpp" +#include "system/bsdl_check.hpp" #include "system/connect.hpp" #include "system/modules.hpp" #include "system/nets.hpp" @@ -99,16 +100,33 @@ Component Tui::BuildAnalyzeScreen() { + anomaly_kind_name(a.kind) + "] " + a.message); + // Model-driven checks (same as `verify`), reusing the nets above. + std::vector model_anoms; + { + auto a1 = check_pin_specs(sys.get(), &nets); + auto a2 = check_jtag_chain(sys.get(), &nets); + auto a3 = check_source_conflicts(sys.get()); + model_anoms.insert(model_anoms.end(), a1.begin(), a1.end()); + model_anoms.insert(model_anoms.end(), a2.begin(), a2.end()); + model_anoms.insert(model_anoms.end(), a3.begin(), a3.end()); + } + for (const auto &a : model_anoms) + analyze_issues.push_back(std::string("[") + + anomaly_kind_name(a.kind) + "] " + + a.message); + int n_model = (int)model_anoms.size(); + if (analyze_issues.empty()) analyze_issues.push_back("(no issue found)"); if (analyze_issue_idx >= (int)analyze_issues.size()) analyze_issue_idx = (int)analyze_issues.size() - 1; std::string issues_header = "Issues (" + std::to_string(n_role_mismatches + n_inconsistent - + (int)rep.anomalies.size()) + + (int)rep.anomalies.size() + n_model) + ": " + std::to_string(n_role_mismatches) + " pin-role, " + std::to_string(n_inconsistent) + " net-mix, " - + std::to_string(rep.anomalies.size()) + " struct.)"; + + std::to_string(rep.anomalies.size()) + " struct, " + + std::to_string(n_model) + " model)"; // ============================================================ Groups analyze_groups.clear(); diff --git a/src/tui/screen_dashboard.cpp b/src/tui/screen_dashboard.cpp index 69223bf..69b4f79 100644 --- a/src/tui/screen_dashboard.cpp +++ b/src/tui/screen_dashboard.cpp @@ -2,6 +2,7 @@ #include "tui/tui_helpers.hpp" #include "system/analysis.hpp" +#include "system/bsdl_check.hpp" #include "system/connect.hpp" #include "system/modules.hpp" #include "system/nets.hpp" @@ -149,6 +150,14 @@ Component Tui::BuildDashboardScreen() { + std::to_string(orph_imported) + " imported, " + std::to_string(orph_dropped) + " dropped)")); + // Model-driven checks (BSDL pin specs, JTAG chain, source conflicts), + // reusing the nets computed above. + int n_model = (int)(check_pin_specs(sys.get(), &nets).size() + + check_jtag_chain(sys.get(), &nets).size() + + check_source_conflicts(sys.get()).size()); + health_rows.push_back(health_line(n_model == 0, + "model: " + std::to_string(n_model) + " BSDL/JTAG anomaly(ies)")); + // ---- analysis summary ---- AnalysisReport rep = analyze_system(sys.get()); int n_diff = 0, n_diff_bus = 0, n_bus = 0;