Open-source, local-first tooling for conducted-EMI pre-compliance workflows: model PCB parasitics, generate LISN and cable-aware LTspice testbenches, run them in your local LTspice, compare EMI filter variants, parse .raw/.log results, and document risks before the lab. A deterministic engineering core — with an optional, opt-in LLM-assisted review layer on top. Results are engineering diagnostics, never certification.
Open EMC is an open-source initiative for transparent, local-first EMC tooling. EMC-Assist is its first concrete tool — a conducted-EMI workflow for LTspice-based simulations. The deterministic core runs entirely on your machine, with an opt-in LLM layer on top.
← Explore the Open EMC initiative| Initiative | openemc.dev |
| This project | openemc.dev/emc-assist |
| Docs | /emc-assist/docs |
| Source | github.com/RobertMalczyk/emc-assist |
A clean LTspice schematic looks reassuring — and omits almost everything that actually shapes conducted emissions. EMC-Assist exists to make those omitted effects visible early, as engineering hypotheses to test, while changes are still cheap.
Trace inductance, via ESL, capacitor mounting effects, plane coupling and filter Q create resonances the ideal schematic never shows.
The LISN, the input cable and the return path set what an EMI receiver would actually measure — not the bare DUT.
Chamber time and re-spins compound. Surfacing likely risks before the booking turns blind guesses into targeted checks.
A deterministic spine writes an auditable artifact at every stage — nothing is a black box. After simulation, an LLM panel fans out over the result, then converges into one diagnosis.
A pre-composition LISN-mode agent also shapes stage 04 before simulation. The deterministic core runs with or without the LLM layer — it is an assistant, never the source of truth.
A deterministic engineering core, with an optional LLM layer bolted on top — never the source of truth.
Every net gets a parasitic: shunt-C universally, series R+L+C where the topology allows a clean cut. Min·typ·max bands, project overrides, and an opt-in LLM negligibility screen.
Parasitics are uncertain, so we sweep them. Each run produces ranked min/typ/max variants — an honest spread, not a single "certain" number.
Peak, quasi-peak-like (meter-time-constant model) and average diagnostic metrics, compared against a configured reference curve — an uncalibrated pre-compliance diagnostic, not a certified-receiver reading.
One focused LLM call per area (DC/DC, filtering, layout, decoupling, parasitics…), plus a pre-composition LISN-mode agent, an orchestrator and a diagnostic synthesiser.
Curated EMC seed rules, local embeddings and a pure-numpy vector index — no FAISS/Chroma, no cloud index. Every rule carries a source or is flagged as an engineering estimate.
Outbound LLM payloads carry only rule_id + source_id + our summary + a ≤200-char excerpt. Full vendor text and your netlist never leave the machine.
Markdown / HTML (PDF optional) with an assumptions table, parasitics table, before/after, a risk list, cited sources and a pre-compliance disclaimer.
A pywebview shell over the same service core: Projects → Report, with stage gating, live logs, the detector spectrum and a stale-data guard.
LTspice runs locally and is never bundled. Cloud LLM is opt-in and key-gated; with it off, no network calls happen under any action.
Import an .asc/.cir, parse R/L/C/V/I/X/M/D + .model/.param/.tran, and derive net roles (power / switch / signal / return) for the analysis.
An opt-in LLM negligibility screen pre-deselects nets that won't move the conducted band — you keep the final say. Key-gated; deterministic otherwise.
One batched LLM+RAG pass refines every net's R/L/C into citation-backed min·typ·max. Preview → review → apply; the deterministic prior is the fallback.
Two time-aligned panels: the LISN-measured V(meas) over a comparison trace (load current by default; four more LLM/heuristic-picked for EMI relevance).
Quasi-peak-like diagnostic at a single frequency with margin to the configured reference curve, or a full sweep across the conducted band (150 kHz–30 MHz) — read straight off the run's .raw.
Every finding carries problem · evidence · proposed change · value range · assumptions · limitations · sim/measurement requirement · confidence · severity · sources.
Decide on each recommendation; decisions persist to decisions/*.json and flow into the report's decision log.
A deterministic, free check of your .tran window/timestep against the conducted band and switching edges — review proposed settings before they apply.
Edit any upstream input and the whole downstream chain is flagged stale; Results / Findings / Report refuse to present old numbers as current.
A typed service/ layer is the product; the CLI and the desktop app are thin adapters over it, with a structured logging seam and offline schema validation.
The composed testbench is also emitted as an .asc you can open in LTspice to eyeball the LISN + cable + injection wiring.
PCB parasitics dominate conducted EMI and they're uncertain — so the tool estimates a min·typ·max band for every net from role-tuned geometry, cites a rule (or marks it an engineering estimate), injects them, and assembles a LISN testbench around your circuit. An LLM can screen out negligible nets and refine the values against the knowledge base — both opt-in, both reviewable.
| Structure | Type | min | typ | max | conf | src |
|---|---|---|---|---|---|---|
| trace R 25×0.5 1oz | R mΩ | 20.2 | 25.3 | 31.6 | high | R001 |
| trace L iso 25×0.5 | L nH | 12.6 | 25.2 | 37.8 | med | R002 |
| trace C Z0 25–50 | C pF | 2.34 | 3.35 | 4.35 | high | R004/5 |
| plane-pair C 100mm² | C pF | 1.90 | 2.38 | 2.97 | med | R012 |
| via L h1.6 d0.3 | L nH | 0.91 | 1.30 | 1.82 | med | R010 |
| loop self-resonance | MHz | 438 | 548 | 685 | high | R030 |
Models cover trace R/L/C, vias, plane pairs, cables and capacitor ESR/ESL/SRF. Injection: a shunt-C on every net, plus a series R+L+C where the net is a clean point-to-point cut; each L gets a Q-damping resistor so tiny pF/nH tanks don't blow up transient time. --parasitics-report-only keeps estimates in the report but out of the sim; project overrides pin any value.
The opt-in negligibility screen reads the per-net plan and pre-deselects nets that won't change the conducted band. For case_003 the 17 LTC7800 controller-pin nets are skipped, leaving 14 power/switch/signal nets.
A single batched LLM+RAG call turns each net's rule-of-thumb band into a citation-backed min·typ·max. You review the proposals (with their cited sources) and apply only the typ overrides — no second call, full audit kept.
V_RAIL → LISN+ → cable → [TRACE_RLC] → DUT (31 nets) → LISN- · DM+CM probes
Signals probed Vout=V(OUT), Vin=V(IN) · supply VIN ↦ LISN · return 0 ↦ LISN · wiring/parasitics/signal audits all green
A real per-frequency quasi-peak-like detector sweep from the bundled case_003 example — an LTC7800 buck (12 → 3.3 V) — against a configured reference curve. The detector flags a low-frequency quasi-peak-like exceedance above that reference curve in this simulation: a pre-compliance hypothesis to confirm on the bench.
Real artifact: results/spectrum.json · 96-point sweep · 150 kHz–30 MHz · trace V(meas). Margins are pre-compliance estimates against a configured reference curve — not a calibrated lab measurement.
Entirely optional and off by default. The deterministic core produces the result on its own; when you opt in and supply a key, this experimental layer reviews it. After simulation, 11 area specialists each produce structured recommendations; an orchestrator clusters them and a synthesiser writes one top-level diagnosis. A pre-composition LISN-mode agent shapes the testbench. Each is a single focused call — no autonomous loops, and the engineer keeps the final say.
“Switch-node (hot-loop) dv/dt likely dominates conducted EMI, with input-filter resonance a secondary contributor.” The simulation shows differential-mode amplitude far above common-mode (20.85 V vs 10.40 V) — consistent with DM-driven emission from a high-dv/dt node.
| Always sent | Never sent |
|---|---|
| rule_id · source_id | your schematic / netlist |
| our own summary | full vendor document text |
| ≤200-char excerpt* | net names (redacted) |
*only from permissively-licensed sources. Every outbound payload is logged to results/llm/*.jsonl. Cloud LLM is off by default.
A slice of the real findings for case_003 — 11 areas, ~37 recommendations:
By default, nothing leaves your computer. The optional cloud-LLM layer is key-gated and off until you turn it on — and even then it only ever sees a redacted, structured summary, never your design files.
With cloud LLM off, no network calls happen under any action.
Every outbound payload is logged to results/llm/*.jsonl — a privacy audit trail you can inspect.
EMC-Assist does not ship a hidden knowledge dump. The core is open source, and users can index sources they are legally allowed to use.
You can read exactly how each parasitic value is estimated, and where the result is an engineering assumption rather than a measurement.
Index the references you're legally allowed to use. No bundled scraped RAG dump; no paid standards shipped inside the tool.
Every rule carries a source or is flagged as an engineering estimate — guidance is never presented without showing where it came from.
A typed service/ layer is the product; the CLI and the desktop app are thin adapters over it. The deterministic spine writes an auditable artifact at every stage; the agent, RAG and LLM layers sit on top — opt-in, and never the source of truth. Everything runs locally: LTspice is a local subprocess, and only redacted, structured payloads ever leave the machine, only when cloud LLM is explicitly enabled.
One typed application core — a plain function per use case returning a result dataclass. Both front-ends are thin adapters, so behaviour can't drift between the CLI and the app.
Every stage writes a JSON/SPICE artifact under generated/ · results/ · reports/ — nothing is a black box, and the whole chain is reproducible from the inputs.
The deterministic core needs no network. Cloud LLM is opt-in and key-gated; every outbound payload is redacted (rule_id+summary, ≤200-char excerpt) and logged to results/llm/*.jsonl.
A local referencing.Registry resolves every cross-$ref offline, so all artifacts are schema-validated without touching the network.
Components log under emc_assistant.<area> to a console handler, an optional per-run JSONL file, and a UI hook — replacing ad-hoc prints.
A localhost service + thin client: the pipeline runs out-of-process so a UI crash can never lose a run, with pull-based logging and a content-aware freshness model. The service/ layer stays the core.
The pywebview shell reads the very same artifacts shown above. Below: the full app — left rail with pipeline gating, topbar and the active screen — then each screen on its own. (Renderings reproduce the live screens with real case_003 values.)
Switch-node (hot-loop) dv/dt likely dominates conducted EMI; differential-mode amplitude far exceeds common-mode.
↓ the detector spectrum, waveform analyzer and corner-variant ranking follow in the live screen
Switch-node dv/dt likely dominates conducted EMI…
Corner ranking: 11 variants · worst par-trace-R…max 48.10 dBµV · baseline 47.08
| Net | Role | Type | R mΩ | L nH | C pF | |
|---|---|---|---|---|---|---|
| ■ | N003 | switch | series-RLC | 2.7 | 4.62 | 1.07 |
| ■ | OUT | power | shunt-C | 7.6 | 23.4 | 4.02 |
| ■ | N007 | signal | series-RLC | 20.2 | 19.3 | 2.68 |
| ▢ | MP_01 | signal | shunt-C | 20.2 | 19.3 | 2.68 |
Audit → 6 series · 7 shunt · 17 dropped (user) · 1 input-rail TRACE_RLC
V_RAIL → LISN+ → cable → [TRACE_RLC] → DUT (31 nets) → LISN- · DM+CM probes
composedActions: View testbench.cir · Continue to run → (auto-starts the pipeline)
Accept / reject each → persisted to the report's decision log.
Edit any upstream input and the whole downstream chain is marked stale on the rail; Results, Findings and Report dim their numbers and show this banner. Stale data is never presented as current.
OpenAI gpt-5-mini — opt-in, key-gated, budget-capped. Deterministic fallback when off.
Local sentence-transformers (POC) behind a pluggable interface; pure-numpy vector index.
Your local LTspice (e.g. 26.0.2) — discovered, never bundled, never hosted.
Schematic → testbench → variants → LTspice → metrics → ranking → report; per-net parasitics, 11 agents, RAG, redaction, CISPR-like diagnostic metrics and configured reference curves.
pywebview shell over the service core — Projects → Report wired to real artifacts, with the stale-data guard.
CSTRAY → earth, so the common-mode metrics and plot carry a real deterministic signal.
From-the-studs remake: localhost service + thin client, out-of-process pipeline, content-aware freshness, large-.raw streaming, pre-run cost/privacy gate.
Overlay a live EMI-receiver spectrum on the simulated prediction; real-time attribution to a specific parasitic.
Your estimated→corrected overrides become opt-in, federated, redacted training signal — the tool improves with use.
What's known to be missing or rough, kept in the open. Everything below is planned or a tracked caveat — not yet shipped.
The canonical receiver-like sweep (Mode 3, 128 pts) under-reads narrow harmonics that fall between swept points. Make detector mode / start-skip / point density user-selectable, and root-cause the skip anomaly.
The tool netlists but doesn't yet understand the schematic — net roles are heuristic. Label power stage, high/low-side switches, SW node, hot loop and rails as user-confirmable, sourced hypotheses.
Diagnose DM/CM noise from the detector result, size a π-filter / CM choke (banded, with Y-cap leakage + damping guards), and close the loop: inject → re-simulate → adjust to meet the limit.
Infer the switching-edge rate from the FET part / topology + KB, so fast-edge devices get a device-aware timestep verdict without the user supplying a rise time.
Review whether the min/typ/max corner sweep covers the uncertainty and propose additional sensitivity / what-if variants as an accept/skip list — additive only, never replacing the deterministic baseline.
Direct strategy in natural language ("add a series L to every net with a 10 pF shunt"); the LLM proposes RAG-grounded per-net edits as a reviewable diff — never a silent mutation.
Opt-in, dual-LISN-only stray capacitance to earth from high-dv/dt nets, so the CM metrics and CM detector plot carry real deterministic signal (today they exist but are inert).
A cross-cutting assurance pass before wider distribution: redaction-path audit + outbound-payload guard, key-never-logged, bridge path-traversal review, parser fuzzing, CSP, and a pip-audit scan.
Localhost service + thin client: crash-isolated runs, pull-based log streaming, content-aware freshness, large-.raw streaming, a faithful per-net testbench render, and a pre-run cost/privacy gate.
Detect max |di/dt| / turn-on inrush and auto-size the .tran window so a slow turn-on event isn't truncated — distinct from the steady-state conducted-EMI run.
Support LTspice fastaccess-format .raw and a real two-LISN V(CM) (today single-LISN V(CM) is a placeholder).
Project history beyond one project, richer stack-up / cable profiles, daily/monthly budget caps (M6); layout import, parasitic extraction and radiated-risk estimation (M7).
The bundled case_003 example walks the whole pipeline on a DC/DC buck converter — so you can see the shape of a run before pointing it at your own project.
A buck converter (12 → 3.3 V) with an input cable and a LISN at the supply — the conducted-EMI measurement setup, in simulation.
Per-net min·typ·max parasitics are estimated and injected, then swept so the result is an honest spread rather than a single "certain" number.
EMI filter variants are compared and ranked against the configured reference curve to show which direction reduces risk.
Being clear about the boundaries is part of being honest. EMC-Assist is a pre-compliance engineering aid — it is deliberately none of the following.
The metrics are quasi-peak-like diagnostics, not a calibrated CISPR-16 receiver reading.
Every result is an engineering hypothesis that requires laboratory verification.
It does not certify compliance with any standard and ships no paid standards.
Independent project. LTspice is user-supplied and runs locally; it is never bundled, hosted, or modified.
It reads your netlist and composes a testbench — it does not draw or edit your board.
Local-first by default. There is no account, no subscription, and no required cloud service.
The shape of a first run. See the documentation for exact, verified commands — the steps below are an outline, not copy-paste commands.
| LTspice | user-supplied · local |
| Python | core package |
| Cloud LLM | optional · off by default |
| Network | not required for a run |
The deterministic core runs with the LLM layer entirely off — it is an assistant, never the source of truth.