Dashboard + palette + analyze screen; consolidated categorization rules.

UI restructuring:

- Dashboard (`screen_dashboard.cpp`, `screen_idx = 6`) is the new home
  screen at boot. Reads Overview / Health / Analysis / Modules from
  the current System every frame; per-module rows list parts grouped
  by `connector_type` and a Power/Gnd inference summary (yellow when
  any name-Power signal is refuted). Scrollable via PgUp/PgDn/Home/End.
  Letter shortcuts: `c`=console, `s`=search, `p`=plug (alias of
  connect), `t`=set-type, `e`=explore, `n`=net, `a`=analyze, `q`=quit.
- Global Ctrl-P palette (`screen_palette.cpp`) — fuzzy-finds over
  registered commands + module / signal names. Activation runs the
  bare command or jumps to the matching screen with state seeded.
- Unified analyze screen (`screen_analyze.cpp`, `screen_idx = 7`):
  tabbed layout (`Issues / Groups / Types`), Tab or ←→ to switch
  tabs, ↑/↓ to navigate the focused list. Replaces the previous
  shell-bouncing `[v]erify` shortcut — `verify` content is now in
  the Issues tab. Types tab attaches the decision rationale to each
  signal row (fan-out / voltage / hard floor).
- Context help panel: `RenderHelpPanel(title, entries)` in
  `tui_helpers.{hpp,cpp}` rendered on the right of every screen.
- Console (former "log") rename: screen 0 is `[c]onsole` in the UI
  and "console" in its help-panel title. The underlying screen and
  the shell prompt are unchanged.
- Esc from any non-home screen returns to the dashboard. The
  dashboard itself swallows Esc; quit via `q` / the `quit` command.
  `quit` now calls `screen_ptr->Exit()` directly so it works from
  any screen including via the palette.

Signal type inference:

- `Signal::type` defaults to `Other` — auto-inference no longer
  happens at construction.
- `infer_signal_types(System*)` is called at the end of every load.
  Three rules: GndShield from name alone; Power requires name match
  + a hard fan-out floor (< 3 pins = always Other, regardless of
  name or voltage) + at least one positive structural signal
  (fan-out ≥ 4 OR voltage pattern in the name like `3V3`, `5V`).
- Thresholds exposed in `analysis.hpp` (`POWER_FANOUT_HARD_FLOOR`,
  `POWER_FANOUT_CONFIRM_MIN`, `has_voltage_pattern`) so the analyze
  screen can render the same rationale without duplicating logic.
- `set-signal-type` still wins; save/restore round-trips the type.

Analysis groups & anomalies:

- New `GroupKind::DiffBus` — ≥ 2 diff pairs sharing the same
  outer-stem with consecutive integer indices are aggregated into a
  single bus (`MDI[0..3]_P/N`). `MDI0` and `PCIE_TX_0` index forms
  both accepted. Solo pairs under a bus-able stem fall back to
  `DiffPair`.
- New `AnomalyKind::DiffBusGap` for missing lanes.

Documentation:

- `DESIGN.md`: dedicated "Categorization rules (normative)" section
  consolidating signal type, NC origin, signal groups, anomalies,
  component kind, and connector wiring rules with exact thresholds
  and decision order.
- `doc/user/analysis.md` (new): user-facing version of the same
  rules in plain language. Linked from `doc/user/index.md`.

Tests: +6 new cases (62 total). Adjusted `test_persist.cpp` to set
the signal type explicitly in the fixture (no more auto-inference).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-14 20:23:33 +02:00
parent 5e89b33088
commit 90502c0762
22 changed files with 1608 additions and 58 deletions

136
doc/user/analysis.md Normal file
View File

@@ -0,0 +1,136 @@
# essim — how the analysis classifies things
essim looks at signal names and the way pins are wired to decide
whether a net is a **power rail**, a **ground**, a **diff pair**, a
**bus**, etc. This page summarises those rules in plain language so you
know what to expect when you run `analyze` (the `[a]` shortcut on the
dashboard) or when you read the numbers on the home screen.
Nothing here mutates anything you cannot fix manually: every
inference can be overridden with `set-signal-type`, and the rules are
re-run on every `load` so the picture stays consistent with the
netlists currently in memory.
## Signal type — Power / Gnd / Other
Every signal is classified into one of three buckets.
**Gnd** if the name matches one of:
`GND`, `GROUND`, `EARTH`, `SHIELD`, `CHASSIS` (or starts with any of
those followed by `_`). The name alone is enough — false positives
here are essentially nil.
**Power** is a two-stage decision:
1. The name has to suggest power — it contains `PWR`, `POWER`,
`VCC`, `VDD`, `VEE`, `VSS`, `VBAT`, or starts with `VS_`, `VS3_`,
`+5V`-style or `-12V`-style prefixes.
2. The wiring has to corroborate it. essim requires at least one of:
- the signal lands on **4 or more pins** (a real rail goes to
decouplers + ICs + connectors, so it almost always has many
pads), or
- the name contains a **voltage value**`3V3`, `5V`, `12V`,
`0V9`, `5V0`, etc. (any `V` next to a digit).
*Hard floor*: a signal touching **fewer than 3 pins** is
**never** Power, even if both 1 and the voltage motif are
present. Physically you cannot have a rail on 1 or 2 pads.
**Other** in every other case.
This rule deliberately rejects things that look like power but
aren't: `PWR_OK` (status), `VSEL_0` (voltage select), `VDD_SENSE`
(sense feedback) — they all match step 1 but fail steps 2/3. The
analyze screen lists them under **Suspect Power** with the reason
attached (`fan-out 1, no voltage` etc.). Inspect, then either accept
the suspect status or force it back with `set-signal-type`.
## NC (no-connect) pins
A pin is shown as `(NC)` in the explore detail when it has no signal
attached. essim distinguishes three reasons:
- **Imported NC** — the netlist explicitly says the pin is
unconnected (Mentor format: signal name `unconnected` or
`unconnected (by TERM)`; Altium format: the pin is simply omitted
from every signal block).
- **Dropped singleton** — after import, essim removes every signal
that touches exactly one pin. A net with a single endpoint cannot
carry signal anywhere, so the pin is detached and tagged. This
catches both intentional sentinels and the per-IC `NC_*` labels
that customers often put on dead pads.
- **Filled at connect** — when you `connect` two parts that don't
agree on which pins exist (a Mentor part may have all pads, an
Altium part only the wired ones), essim materialises the missing
pads on the smaller side. They are unconnected *locally* on that
module but are bridged to a real signal on the other module via
the connection — so they do not count as orphans.
The dashboard's "NC" row summarises orphan counts (imported and
dropped only; filled-at-connect pins are excluded). The analyze
screen's "Types" tab adds a trailing line with the totals.
## Signal groups
essim groups signals that share an obvious structural pattern. They
are detected per module — a multi-card bus on the system is the BFS
union of the per-module groups it touches.
**Diff pair** — two signals named `STEM_P` and `STEM_N`
(case-insensitive, `_` required before the polarity letter). Both
halves must be present. Lone `_P` halves are flagged as orphans;
lone `_N` halves are *not* flagged (the `_N` suffix is overloaded
with active-low semantics — `RESET_N`, `BOOTMODE_N` — and flagging
them would flood the report).
**Diff bus** — at least two diff pairs whose stems share a common
prefix and only differ by a trailing index: `MDI0_P`/`MDI0_N`,
`MDI1_P`/`MDI1_N`, … → `MDI[0..3]_P/N`. Both `STEMN` and `STEM_N`
forms work (`MDI0`, `PCIE_TX_0`).
**Bus** — at least two signals with a common stem and a trailing
integer index. Two notations: `DATA[0]`, `DATA[1]`, … (bracketed)
or `ADDR_0`, `ADDR_1`, … (underscore — *strict*: an underscore is
required between the stem and the digits, so a name like
`GETH_01_VDD12` is *not* a bus).
**Anomalies** are emitted alongside groups:
- *Diff pair orphan*: a `_P` with no matching `_N`.
- *Diff bus gap*: e.g. `MDI[0..3]` has `MDI0`, `MDI1`, `MDI3` (`MDI2`
missing).
- *Bus gap*: same idea on plain buses.
Internal Mentor net names that start with `$` (like `$N12345`) are
skipped from every group/bus detection.
## Issues reported by `analyze`
The Issues tab of the analyze screen aggregates everything that
deserves attention:
| Tag | What it means |
|---|---|
| `[pin-role]` | A connector pin is typed (via `set-type`) as Power or Gnd but the actual signal landing on it disagrees. |
| `[net-mix]` | A net bridged across modules carries both Power and Gnd signals — almost always a topology mistake. |
| `[diff-pair-orphan]` | `STEM_P` with no `STEM_N` in the same module. |
| `[bus-gap]` | A bus is missing one or more index values inside its range. |
| `[diff-bus-gap]` | A diff bus is missing one or more lane indices. |
Zero issues = the module passes every structural check essim knows
how to run today.
## Overrides
Every classification is advisory. To force a different type:
- **Signal type**: from the `net` or `explore` screen, press Enter
on a signal entry → a popup lets you pick `power` / `gnd` /
`other`. Or type `set-signal-type <module> <signal> <type>` in the
console (or from the palette).
- **Connector type**: `set-type <module> <part> <connector-kind>`
(also via the dashboard `[t]` shortcut). This drives the pin role
expectations, which feed the `pin-role` check.
Overrides survive `save`/`restore` but are recomputed at every
`load` (i.e. the inference re-runs).

View File

@@ -93,6 +93,9 @@ fresh.
- [`commands.md`](commands.md) — exhaustive command reference,
regenerated from the binary on every `cmake --build build --target doc`.
- [`analysis.md`](analysis.md) — how essim classifies signals
(Power / Gnd / Other), how it detects buses and diff pairs, what
the `analyze` screen actually reports and why.
- [`scripting.md`](scripting.md) — `set` / `$var` / `${var}`, `source`
semantics, the script-save denylist.
- [`DESIGN.md`](../../DESIGN.md) — implementation notes, useful if