New pass 8 (core/domain/diff_check.{hpp,cpp}): every complete local diff
pair (X_P/X_N, name-based) resolves its legs to two bridged nets; peer
pairs on those nets must match leg for leg.
- DiffPolaritySwap: P legs meet N legs across a connection (sometimes
intentional - reported for review), or both legs joined onto one net.
- DiffCrossIncomplete: pairs sharing only one leg, and diff-bus lanes
crossing NOWHERE while sibling lanes cross (distributed/fan-out buses
stay silent - validated against the real 7-card VPX system: 21 noisy
findings down to 3 genuine dangling-lane reports, 0 false swaps).
diff_suffix/split_trailing_index/is_internal_name promoted out of
analysis.cpp's anonymous namespace for reuse. VerifyReport.diff_anomalies
wired into model_total() and all four renderers (TUI verify, script
engine, wx log, analyze Issues). 8 new test cases (466 assertions).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
"Adjacent" read as jargon; "power management" is the standard EE umbrella
for enable/power-good/sense/fault/seq signals (cf. PMIC). Renamed across
the board: NameVerdict::PowerMgmt, stats/LoadResult field `mgmt`, analyze
tag [Power mgmt] + header "Pwr-mgmt" + glossary, load lines now say
"power-management (control/measure — kept as Other)" (TUI / script / wx
kept in sync).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Names holding both a rail token (VCC/VDD/PWR/...) and a control token
(SENSE, EN, PG, FB, OK, FAULT, ...) are signals ABOUT a rail - feedback,
enable, power-good - so their non-Power classification is confident.
They used to land in the Suspect bucket, drowning the genuine ambiguities.
- classify_signal_name(): 3-state name verdict (Rail / PowerAdjacent /
GndShield / Other) with whole-token matching (trailing digits stripped,
long lexemes also match as suffix: VSENSE, PWRGOOD, NFAULT).
infer_signal_type() becomes a thin wrapper, so the dashboard suspect
count and the export suspect column shrink automatically.
- infer_signal_types(): PowerAdjacent -> Other + new `adjacent` stat,
before the structural gate (a big-fanout sense net stays Other).
- LoadResult.adjacent rendered by all three consumers (TUI command,
script engine, wx log) - outputs kept in sync.
- analyze Types tab: new [Pwr-adjacent] rows with the deciding token,
deliberate sort order (Power, Suspect, Adjacent, Gnd), glossary entry.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A script using `duplicate` failed with "unsupported command 'duplicate'"
because the clone logic was still inline in the tui command. Extract it to
core/app/edit.hpp::duplicate_module(System*, src, dst) -> {ok, error, parts,
signals}: a deep clone of a module (parts, pins with spec + nc_origin, signals
with type overrides, pin→signal wiring; no connections), refusing on an unknown
source or an already-taken destination name.
- the tui `duplicate` command renders the result (output unchanged);
- the script engine dispatches `duplicate` to it — the failing script now runs;
- the wx GUI gains Edit ▸ Duplicate module… (PickModule + a name prompt).
tests/test_edit.cpp: deep clone wires to the clone's own signal (not the
source's) and preserves the type; unknown source / existing destination
refused. 412 core assertions green; tui + wx build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Real cause of the log not resizing: on GTK a wxTreeCtrl/wxTextCtrl reports a
natural size that grows with its content. Once a script populated the tree with
many pins, the top area's minimum ballooned and consumed the vertical space,
pinning the log at its minimum so it no longer tracked the window (it worked on
an empty tree, hence "only after running a script").
Cap each control's min size (tree/overview 260x120, log 420x90) so content
can't inflate the sizer minimum; the proportions then govern and content
scrolls inside the controls. Keeps the frame sizer from the previous fix.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The frame had no sizer; it relied on the implicit single-child fill, which
didn't reliably resize the panel, so the panel's vertical sizer never
redistributed and the log kept its size when the window grew. Add a frame
sizer holding the panel at proportion 1 + wxEXPAND, so a resize re-lays-out
the panel and the log (1/3) tracks the window.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Now that core has run_script, wire WxFrontend::BootDispatch's `source` to it
instead of the "no script interpreter" note. `essim --source bring-up.essim`
(and with --batch) now executes the script in the wx frontend too, accumulating
the same output the tui produces into the console buffer surfaced by DumpOutput.
Verified: `essim --batch --source script.essim` (wx) runs load + set-signal-type
+ verify and prints the source summary.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The wx frontend had no way to run essim command scripts (only the tui shell
did). Add a frontend-agnostic engine in core/app/script.{hpp,cpp}:
run_script(unique_ptr<System>&, path, ostream&) -> {ok, error, lines, errors}.
It parses essim scripts (# comments, blank lines, "quoted" args, set + $var /
${var} expansion, nested source with a depth guard) and dispatches the
scriptable, system-building commands — new, load, connect, set-connector-type,
set-signal-type, attach-bsdl, verify, export, save, restore — to the existing
app::* operations, writing their (TUI-identical) output to the stream.
Unsupported/interactive commands are reported and counted, execution continues.
System is taken by reference so new/restore can replace it.
wx gains File ▸ Run script…: pick a .essim, run it, echo the output into the
log and refresh. WxFrontend exposes system_ptr() for the engine.
tests/test_script.cpp: a full script (comment + set/$var + new + load + set-
signal-type + verify) builds the system and produces the expected log; missing
file reported; unsupported command flagged and skipped. 400 core assertions
green; tui + wx build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Each tree node now carries a NodeData (kind + Module/Part/Signal pointers), so
edits can act on what's selected instead of always re-asking:
- Set connector type / Attach BSDL / Connect act on the selected Part (or the
Pin's owning part); Connect uses it as the first endpoint, then prompts for
the second. Set signal type acts on the selected Signal.
- With nothing relevant selected, each falls back to its modal picker, so the
menu-driven flow still works.
- Right-click a Part or Signal → a context menu of the actions valid for it;
the items reuse the menu IDs and select the clicked node first, so they run
the same handlers.
wx-only; builds clean, window opens with no asserts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The model tree was modules → parts only. Drill it down:
- each Part now lists its Pins, each labelled with the signal it is wired to
and that signal's type, or "(NC[, imported|dropped])";
- each Module gains a "Signals (N)" branch — the per-module net view, each
signal labelled with its type and fan-out.
Pin/part/signal lists sort in natural order ("2" < "10") via a small helper.
Modules expand to show their parts + Signals node; pins and the signal list
stay collapsed (revealed on demand) so large parts don't flood the view.
wx-only change (no core, no tui); builds clean, window opens with no asserts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fourth editing op into the wx frontend. Extract the type-name parse + apply
into core/app/edit.hpp::set_signal_type(Signal*, name) -> {ok, error, type},
failing without mutation on an unrecognised name. The interactive sigtype modal
keeps its own SignalType-cycling path (different interaction, trivial mutation).
The TUI `set-signal-type` command now renders that result (output unchanged).
The wx GUI gains Edit ▸ Set signal type…: a shared PickModule() helper (PickPart
now builds on it) + inline signal choice + a power/gnd/other dropdown, then the
core op, logged as "module/signal: signal type = …" and reflected.
tests/test_edit.cpp: name parsed and applied; unknown name refused without
mutation. 387 core assertions green; tui + wx build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Third editing op in the wx GUI. No core change — app::connect_parts was already
extracted and unit-tested; this is pure wiring. Edit ▸ Connect parts… picks two
parts (PickPart twice, now caption-parameterised to label "first/second part"),
derives their parent modules from Part::prnt, calls app::connect_parts and
renders the same outcomes the TUI does: refused / identity NC fill / connected
(N wires) / failed.
wx builds clean, window opens with no asserts; tui + tests unaffected.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Second editing op into the wx frontend. Extract the logic (parse the .bsd,
apply it to the part, record bsdl_path) into core/app/edit.hpp::attach_bsdl
(Part*, path) -> {ok, error, entity, bound, unbound, ports_total}, failing
without mutation when the file can't be parsed.
The TUI `attach-bsdl` command now renders that result (output unchanged); the
wx GUI gains an Edit ▸ Attach BSDL… menu reusing PickPart() + a .bsd file
dialog. Prune the now-dead bsdl_model include from commands.cpp.
tests/test_edit.cpp: parse failure leaves the part untouched; null part. The
success path is covered by a batch run (entity + bound/total ports). 381 core
assertions green; tui + wx build clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First of the editing ops to reach the wx frontend. Extract the business logic
(validate the kind, tag the part, apply the connector model) into
core/app/edit.{hpp,cpp}: app::set_connector_type(Part*, kind) -> {ok, error,
materialised}, refusing without mutation when the kind is invalid for the part.
Both TUI call sites now use it: the `set-connector-type` command and the
interactive settype screen (de-dup) — output unchanged. The wx GUI gains an
Edit ▸ Set connector type… menu: a reusable PickPart() (module → part choice
dialogs) + a kind prompt, then the same core op, logged and reflected in the
model tree. Prune the now-dead pin_model/transform_vpx includes from
commands.cpp.
Unit-tested by tests/test_edit.cpp (free-form kind tags; invalid kind refused
without mutation; null part). tui + wx build clean; 376 core assertions green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Document the wxWidgets GUI frontend (wx dependency + -DESSIM_FRONTEND=wx in
README; layout tree + architecture note in DESIGN) and the factored
essim_add_frontend() per-frontend CMake helper.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A native wx GUI built entirely on essim_core via the Frontend interface — no
Tui reuse, no command shell. Demonstrates the core/frontends architecture by
adding a real second frontend:
- WxFrontend : public Frontend — owns the System + a console buffer; handles
boot headlessly (restore for --restore/--batch; honest note for source);
Run() boots wx without a generated main (SetInstance + wxEntryStart/
CallOnInit/OnRun) so the shared frontend_main stays in control.
- EssimFrame (wxFrame) — menu-driven window: Load (app::load_module), Restore/
Save (persist), Export (app::export_connections), Verify (app::verify),
rendered into a model tree (modules → parts), an overview + verify-health
panel, and a log. Each handler is a thin wrapper over a core/app op.
- main.cpp: construct WxFrontend, call frontend_main — same 4 lines as tui.
- CMakeLists.txt: find_package(wxWidgets) + essim_add_frontend(wx LIBS …);
select with -DESSIM_FRONTEND=wx. ESSIM_FRONTEND gains wx in its STRINGS.
Set LC_CTYPE from the environment at GUI init so wxString decodes the UTF-8 in
narrow literals (em dash, ellipsis); LC_NUMERIC stays "C". .gitignore: build*/.
Needs libwxgtk3.2-dev. Verified: builds clean (wx 3.2.9 GTK3), window opens and
renders with no wx asserts; --commands-md/--restore/--batch behave headlessly.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Every frontend repeated the same target wiring (glob sources minus main.cpp →
essim_<name> lib linking essim_core; essim exe from main.cpp linking the lib +
essim_frontend; RUNTIME_OUTPUT_DIRECTORY=build/). Move it into a reusable
helper cmake/EssimFrontend.cmake::essim_add_frontend(name LIBS ...), included
once at the top level.
The tui CMakeLists now just fetches FTXUI and calls
essim_add_frontend(tui LIBS ftxui::screen ftxui::dom ftxui::component). A new
frontend's CMakeLists is its toolkit setup + one call. No behaviour change;
binary stays ./build/essim, tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reorganises essim around a hard core/frontends boundary so multiple GUI/TUI
engines can be built on the same business logic:
- src/core/{domain,imports,app} — frontend-agnostic essim_core (no GUI
toolkit); src/frontends/<name>/ — per-frontend UI. CMake selects one with
-DESSIM_FRONTEND=<name> (default tui; "none" = core+tests, no FTXUI).
- Application layer (core/app/): export / verify / connect / load extracted
as System-in, result-struct-out operations; TUI commands and screens are
thin wrappers. The analyze screen and dashboard now share app::verify
instead of recomputing the passes (de-dup).
- Frontend interface (frontends/frontend.hpp) + shared frontend_main: main.cpp
is frontend-agnostic; a new frontend is a 4-line main plus a Frontend impl.
- ImportBase hardened (read-only open, fail-fast on a missing file) and made
leak-free (valgrind: no leaks, 0 errors).
Tests: 78 core cases / 368 assertions + the TUI suite, all green. Binary stays
./build/essim; --batch/--commands-md/--help behaviour unchanged.
Reflect the new shared frontends layer (frontend.hpp / frontend_main) in the
Architecture section and layout tree, and list the verify/connect/load app ops
alongside export.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
main.cpp was entirely TUI-specific (constructed Tui, parsed argv, drove
BootDispatch/DumpOutput/Run directly). Introduce a shared frontends layer so a
second frontend can reuse the whole launch flow:
- src/frontends/frontend.hpp — abstract Frontend interface (BootDispatch,
DumpCommandsMd, DumpOutput, Run), header-only, no GUI toolkit, no core dep.
- src/frontends/frontend_main.{hpp,cpp} — frontend_main(argc, argv, Frontend&):
all the argv parsing (--source/--restore/--batch/--commands-md/--help) and
the boot → batch/run flow, driving any frontend through the interface.
- Tui now implements Frontend (the four methods already matched; just marked
override).
- The TUI main.cpp shrinks to: construct Tui, call frontend_main. A second
frontend's main() is identical with its own Frontend type.
Build: a small GUI-toolkit-free static lib essim_frontend (frontend_main.cpp)
is added at the top level when a frontend is selected, and the essim exe links
it. ESSIM_FRONTEND=none still builds core+tests only (no essim_frontend, no
FTXUI). Binary stays ./build/essim.
Behaviour unchanged across --batch/--commands-md/--help/exit codes; only the
usage text is genericised ("the TUI" → "the interface", "console screen" →
"console") now that the launcher is shared.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
System::Load never deleted the ImportBase it allocated, and ~ImportBase was
defaulted so the Parts container it held leaked too. Give ~ImportBase a body
that deletes prts (the container only — the Part objects have been transferred
to the Module via add(), which owns them; the default ~Parts frees the map
without touching the elements, so no double free), and delete the importer at
the end of System::Load (and on the early unreadable-file error path).
Drop ImportMentor's explicit destructor, which called ImportBase::~ImportBase()
in its body — harmless when the base dtor was empty, but a double delete of
prts now that it frees memory. The implicit destructor calls the base once.
Verified with valgrind on a batch load (mentor + a missing-file error path):
"All heap blocks were freed — no leaks are possible", 0 errors.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ImportBase opened the input with a default std::fstream (in|out), which had
two consequences: a missing file silently produced an empty module (no error),
and a present-but-read-only file failed to open and also loaded as empty. Open
the stream read-only (std::ios::in) instead, and expose is_open().
System::Load now builds the importer first, checks is_open(), and throws
"cannot open file: <path>" before creating the module — so a failed load
surfaces as `load failed: …` and leaves no empty module behind. A read-only
but present file now loads correctly.
Flip the test that pinned the old silent-empty behaviour to assert the clean
failure (error + no module created).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the import orchestration — System::Load + drop_singleton_signals +
infer_signal_types + the post-import counts — out of the `load` command into
core/app/load.{hpp,cpp}: app::load_module(System*, name, path, ImportType)
returns a LoadResult (ok/error, parts, signals, dropped, power/gnd/kept_other)
with no Print/dialog/FTXUI. The "mentor|altium|ods" string→enum mapping moves
to app::import_type_from_name (mirrors export_format_from_path). The command
only parses the type and renders the counts; output is byte-identical.
Add tests/test_load.cpp (core, no UI): the name mapping; a minimal Mentor
netlist that imports two parts and drops one singleton signal; and a pin test
of the pre-existing missing-file behaviour (ImportBase doesn't check is_open(),
so a missing file yields an empty module rather than an error — preserved by
the extraction and pinned so any future hardening is a deliberate change).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the wiring orchestration — transform lookup, identity-compatibility
check, identity NC fill, transform apply, Connection creation/add — out of
the `connect` command and the interactive connect screen into
core/app/connect.{hpp,cpp}: app::connect_parts(System*, m1,p1, m2,p2) returns
a ConnectResult (ok/refused/error, connection_name, transform_name, wires,
identity_info, nc_added) with no Print/dialog/FTXUI. Name/pattern resolution
and ambiguity reporting stay in the command — that is arg-parsing, not the op.
Both frontends now call the one core op, removing the duplicated logic. This
also unifies a divergence: the interactive screen previously called
CheckIdentityCompatible without the info pointer and so never filled identity
NC pins (unlike the command); routing it through app::connect_parts makes the
screen fill NCs and surface the same warning, matching the scriptable path.
Command output is unchanged. Prune the now-dead transform.hpp / domain
connect.hpp includes from the frontends (commands.cpp keeps transform_vpx.hpp
only for ValidatePartForKind).
Add tests/test_connect.cpp (core, no UI): identity-compatible pair wires,
unknown type pairing is refused with nothing created, subset side gets NC
pins filled and the warning reported.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The analyze Issues pane and the dashboard Health rows each recomputed the
same verify passes inline (pin-role mismatches, Power/Gnd net-mix, NC orphan
rollup, model-driven checks) — the third and second copies of what the verify
command also did. Route both screens through app::verify(System*) instead, so
the passes live in exactly one place.
Enrich VerifyReport with a per-pin OrphanPin detail list (module/part/pin +
dropped flag) so the dashboard can still nest its dropped-singleton breakdown
under the NC health line without re-walking modules/parts/pins. Output is
unchanged in both screens (same label formats, same numbers).
Prune the now-dead includes (nets/bsdl_check/connect/parts/pins as applicable,
<unordered_set>) from both screens. Extend tests/test_verify.cpp to cover the
new orphans detail.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the 7-pass verify orchestration out of the TUI command lambda and
into core/app/verify.{hpp,cpp}: app::verify(System*) returns a structured
VerifyReport (role mismatches, net inconsistencies, orphan counts, the four
model-driven anomaly vectors) with no Print/dialog/FTXUI. The nets are
computed once and fed to the net-based checks.
The verify command is now a thin renderer over the report, byte-identical
output. Prune the now-dead nets.hpp / bsdl_check.hpp / <unordered_set>
includes from commands.cpp.
Add tests/test_verify.cpp: builds small systems by hand and asserts the
report (empty system, Power/GndShield bridged-net inconsistency, orphan
counts by import origin) — pure core, no UI.
This is the structuring extraction: the same VerifyReport can now back the
analyze screen's Issues pane and the dashboard health rows, removing the
triple duplication of passes 1-3.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
DESIGN.md: new layered Build section (essim_core + ESSIM_FRONTEND, FTXUI per
frontend, split tests), a rewritten Layout tree (src/core/{domain,imports,app},
src/frontends/tui, tests/{,tui}), and a new "Architecture — core vs frontends"
section stating the rule (core never depends on a frontend) with the export
operation as the worked example. README: layered-build note, FTXUI-is-the-tui-
frontend's dependency, core-vs-frontend test split, nested project-layout tree.
Also keep the binary at ./build/essim via RUNTIME_OUTPUT_DIRECTORY now that the
exe is produced from the frontend subdir.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reorganise the tree into business vs frontend as separate directories:
src/core/{domain,imports,app} (was system/, imports/, app/)
src/frontends/tui/ (was tui/ + main.cpp)
tests/tui/ (the FTXUI-coupled helper test)
All cross-dir #include paths rewritten; same-dir includes untouched.
CMake: essim_core is the frontend-agnostic business library — links libzip,
pugixml and bsdl, NO GUI toolkit. Each frontend is a self-contained
src/frontends/<name>/ (own CMakeLists, toolkit, main.cpp) that links
essim_core, selected with -DESSIM_FRONTEND=<name> (default tui; 'none' = core +
tests only, no toolkit fetched). FTXUI moved into the tui frontend. Tests are
split: essim_tests links essim_core (no FTXUI), essim_tui_tests links essim_tui.
Verified: default tui build green (ctest 2/2); ESSIM_FRONTEND=none builds the
core + tests with FTXUI never fetched and no `essim` binary.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
First step of separating business logic from the TUI. The export command built
the CSV/ODS file inside its lambda, mixed with Print/ShowError/dialog calls.
Move all of it — CSV + ODS building, sheet-name sanitising, file writing — into
src/app/export.{hpp,cpp} (namespace app, no FTXUI/console dependency):
export_connections(const System*, path, format) -> ExportResult. The TUI
command is now a thin wrapper (resolve args/dialog, call the core, render). The
core is unit-tested without any UI (test_export); 342 assertions pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The Renderer-overlay approach to the global progress box didn't render. Use a
proper Modal like the palette / file dialog, driven by a plain bool
'computing_open' raised when a source starts and lowered when it ends or
aborts. The tick handler stays ahead of the modal guard, so the script keeps
running (and the screen behind it keeps updating) while the modal is shown;
computing_open is also added to the guard so stray keys are ignored mid-load.
The console screen's own Computing block was already removed, so no duplicate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A script opened from the dashboard (screen_idx 4) aborted after its first
line: the guard treated any non-console screen as 'an interactive command
opened a screen'. Record the screen the source started from and abort only
when a sourced line navigates away from it (what a bare interactive command
does). Now 'o' from the dashboard runs the whole script in place — the
dashboard populates live behind the global Computing overlay — while a bare
connect/explore inside a script still aborts. Batch unaffected (BootDispatch
pins screen 0, so origin 0).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Opening a script from the dashboard did nothing visually: the tick driving the
line-by-line loader was only handled in the console case, and the Computing…
overlay was console-only. Move both to the global layer — ticks now process on
any screen (the dashboard updates live as the script loads) and the Computing…
box overlays whatever screen is active. Add an 'r' dashboard shortcut to
restore a snapshot via the file picker (open mode, like 'o'). Dashboard help
hints (loaded + empty state) updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The picker is built for saving, so it asks 'file exists — overwrite?' on
confirm. That's wrong when opening a script (you want an existing file). Add a
confirm_overwrite flag (default true; FileDialogState + OpenFileDialog param);
ConfirmFileDialog only prompts when it's set. The dashboard 'o' shortcut now
opens the dialog with confirm_overwrite=false. save/export keep the guard.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The empty-dashboard panel (early_help) is a separate hint list from the
loaded-system one, and didn't list the new o/s keys — so 'o' worked but wasn't
advertised when no system is loaded (exactly when you'd use it). Add 'o' to
early_help and mention it in the 'no system loaded' line. (s/x need a system,
so they stay out of the empty-state panel.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Two new dashboard keys, mirroring the existing x=export: 'o' opens a file
picker to run a .essim script (-> source <path>), 's' opens a file picker to
write a system snapshot (-> save <path>, only when a system is loaded). Both
use OpenFileDialog with a per-key persisted last directory; the global
CatchEvent already yields to the file-dialog modal while it is open. Dashboard
help hints updated.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The detail block was rendered after *all* health rows, so it dangled below
the new model: row instead of the NC: row it explains — its indentation read
as broken. Build it into health_rows right after the NC row, with a tree
marker (↳ dropped (only 1 pin on the net):) and consistent nesting.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace internal jargon and uncommon words in user-facing strings (better for
non-native English readers): drop "TransformRegistry-driven" / "drives
transforms" from the connect/set-connector-type subtitles; "transform lookup"
→ "tells connect how to wire its pins"; "populate pin specs" → "fills in each
pin's role and direction"; "clear the visualization area" → "clear the console
output"; "materialised" → "added"; refresh the verify description. Regenerated
commands.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
"dropped detail:" said nothing about what those pins are. They were detached
by drop_singleton_signals because each was the lone pin on its net (nowhere to
connect → NC). Relabel to "dropped — lone pin on its net (→ NC):".
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
check_bsdl_completeness(System*): for each BSDL-attached part, re-parse the
.bsd and report the device power/ground ports with no matching pin on the
netlist part (matched by port name or physical pad) — a rail the schematic
symbol is missing. One aggregated BsdlPinMissing per part; restricted to
power/ground so unused I/O balls don't create noise. Surfaced as a 7th verify
pass and in the analyze/dashboard model counts. 76 cases / 327 assertions
green; the real 8-card system reports 0 (all FPGA rails present). This closes
out P3.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The analyze screen's Issues pane now lists the model-driven checks
(check_pin_specs / check_jtag_chain / check_source_conflicts) alongside the
pin-role, net-mix and structural ones, with an "N model" count in the header;
the dashboard gains a "model:" health row. check_pin_specs/check_jtag_chain
take an optional precomputed net list, so verify, analyze and the dashboard
each compute the nets once and reuse them across checks instead of redoing the
transitive closure per check. Unit tests (75) green; verify output unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rank the spec sources (spec_source_rank: UserOverride > Bsdl > ConnectorModel
> Inferred > Imported); apply_model now refuses to overwrite a spec owned by a
higher-rank source, so one model never clobbers a more authoritative one. New
check_source_conflicts(System*) emits SourceConflict for a pin the BSDL
declares power/ground (a must-connect rail) that the netlist leaves
unconnected — a rail floated in the schematic; surfaced as a sixth `verify`
pass. Unit tests (75 cases) green; the real 8-card system reports 0 conflicts
(its rails are all connected) while the JTAG findings remain.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New PinModel interface (spec_for / layout / source) + a single apply_model(
Part*, const PinModel&) that materialises missing layout pins and sets each
pin's spec only where the model speaks (spec.source != None), so one source
never clobbers another's. ConnectorModel wraps pin_role/pin_layout;
BsdlPinModel wraps a parsed BsdlModel (indexed by port name and physical pad).
set-connector-type and screen_settype now use ConnectorModel + apply_model;
attach-bsdl and the restore re-apply keep calling apply_bsdl, now a thin
adapter over apply_model. Behaviour-preserving: unit tests (73 cases) green and
the real 8-card system re-runs identically (1517/1517 bound, same JTAG
findings). Covered by test_pin_model.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
DESIGN.md: libbsdl dependency and --batch headless mode; bsdl_model/bsdl_check
in the layout; the attach-bsdl command and the `B` persist tag; PinSpec is now
BSDL-populated; verify's five passes incl. the model-driven and JTAG checks
and the new AnomalyKinds. README: libbsdl dependency, --batch usage, tutorial
link. New doc/user/tutorial.md: end-to-end batch and TUI walkthroughs (load →
tag → connect → attach-bsdl → verify, with the pin/JTAG findings explained).
Regenerated commands.md (adds attach-bsdl); index.md links the tutorial.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Real-data testing (3 BSDL-attached FPGAs in an 8-card system) showed undriven
over-fires when only one side of a net has a known direction: the driver sits
on an un-modelled part (direction Unknown). Require known == net pin count, so
"undriven" is concluded only when every pin on the net is modelled. Drops 216
false positives to 0 on the sample system while the genuine JTAG findings
remain; the unit test is unaffected (its net is fully modelled).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
BootDispatch already runs --restore/--source synchronously before the TUI
starts (Source takes its headless drain branch when no screen is attached),
so the console buffer is complete by then. New --batch flag dumps that buffer
(Tui::DumpOutput) to stdout and exits without launching the TUI — enabling
scripted/CI runs and verify output capture (e.g. essim --batch --source s).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
New bsdl_check.{hpp,cpp}: check_pin_specs(System*) walks the nets and uses
each pin's PinSpec direction/function to flag DriveContention (>=2 push-pull
output drivers), UndrivenNet (a multi-pin net with input(s) but no driver),
and NcWired (a no-connect pin wired onto a multi-pin net). Added as a pass in
`verify`; AnomalyKind extended accordingly. Nets with no direction data are
skipped, so un-modelled parts produce no noise. Covered by test_bsdl_check.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New `attach-bsdl <module> <part> <file.bsd>` command: parse via BsdlModel,
apply_bsdl() onto the part, store the path on Part::bsdl_path, report bound/
unbound. Persist a `B\t<path>` line under the part; on restore, re-parse and
re-apply each attached model (the .bsd path is persisted, not the derived
pin specs). Round-trip covered by test_bsdl_apply.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>