Firmware has a fast feedback loop. You push a commit, CI builds it, flashes it onto a real board, and reports back within minutes. PCB layout does not work that way. You route a board, send it to fab, wait two weeks, populate it, and often only find out at a test house months later that it radiates 8 dB over the limit at 480 MHz. Then you have to work out which trace caused it and respin.
Full-wave electromagnetic solvers have existed for decades, so the physics is not the obstacle. Running one has been a specialist activity: a licence, a workstation, time spent learning the meshing UI, and someone who can interpret the output. That is not something a team does on every commit.
The EMI Analyzer takes the board files you already commit, runs electromagnetic analysis on them, and reports the results against the commit that changed the layout.
Two analysis tiers
The tool does two different jobs on very different timescales, and it is worth being clear about which is which.
The rules tier runs in seconds. It is geometric rather than electromagnetic: no solver, no mesh. It reads your copper and looks for structures that are known to radiate. This is the tier to run on every push.
The solve tier runs openEMS FDTD over a region of the board you select, and takes hours. It produces near-field hotspot maps, a far-field radiation pattern and S-parameters. This is the tier to use once the rules tier has pointed at something and you want to quantify it.
Only the rules tier is suitable for a push trigger. The sections below cover why the solve tier is not.
What the rules tier checks
These are geometric checks with an electromagnetic justification, run against a normalised model of your board. Each finding carries a severity, the net it concerns, and board coordinates, so it links directly to that spot in the board viewer.
| Check | What it finds | Why it radiates |
|---|---|---|
return-via | A layer change with no ground via near it | The return current has to detour to find a path, and the resulting loop acts as an antenna |
plane-gap | A net crossing a gap in its reference plane | The same problem in a worse form: the return current has to travel around the split |
via-stub | The unused remainder of a through via | A quarter-wave stub resonates and couples energy out at a predictable frequency |
radiator | A net long enough to be a significant fraction of a wavelength | Past roughly λ/20 a trace begins to behave as an antenna rather than a wire |
edge-proximity | A net routed close to the board edge | Fields fringe off the edge instead of staying coupled to the reference plane |
These are the same things an experienced hardware engineer looks for in a layout review. Automating them means they get checked consistently, on every change, rather than depending on how much review attention a particular commit happens to get.
The output is a rules.json file with a summary block counting critical, warning and info findings, alongside the maximum frequency the analysis assumed. Those counts are what a CI gate thresholds on.
Putting it in your pipeline
The input is what you already have in the repository. Commit a KiCad project, either the .kicad_pcb or the project as a zip, and push it. The board is ingested, normalised, and the rules tier runs against it. The findings come back attached to that commit.
If you are not using KiCad, the tool also accepts Gerbers, provided you include the drill file and an IPC-D-356 netlist. The netlist is required rather than optional, because Gerbers carry no net information at all. Without it there is no way to tell a signal trace from its own ground pour, which rules out the net picker, the return-path analysis, and placing a port by name. A SPICE netlist does not work either, because it has no coordinates. With the netlist present, nets are reconstructed by rasterising each copper layer, labelling the connected islands, and dropping the netlist coordinates onto them.
A reasonable division of work in a pipeline:
- On every push that touches the layout, run the rules tier. It takes seconds, and it catches the mistake while the routing decision is still recent.
- On a pull request that changes routing, compare against the base branch. The next section covers why this comparison is the most useful output.
- Nightly or on demand, run a solve over the region you care about. That is hours of compute, so run it when nobody is waiting on the result.
Gating on critical > 0 is a reasonable starting policy. Gating on warnings is usually counterproductive: a board of any complexity carries warnings that a human has already reviewed and accepted, and a check that reports problems on every push tends to get disabled.
Setting it up in GitHub Actions
There is a ready-made action, so the setup is short. Add embeddedci-com/embeddedci-github-action/emi to a workflow with a paths filter on your layout files, point it at the board, and give it an API key carrying the emi:analyze scope. There is no toolchain to install and nothing to add to permissions:, because the action authenticates as you rather than as the repository. An EMI run is filed under your organisation.
On each run the action uploads the board, skipping the transfer when the bytes have not changed, waits for the rules tier, and writes every finding back as a GitHub annotation: ::error for critical, ::warning and ::notice below that. The findings appear against the workflow run rather than only in the log. The counts go to the job summary with a link back to the board viewer, and the step's exit code is the gate. fail_on defaults to critical, can be set to warning for a stricter policy, or to none to collect the numbers and decide in a later step.
The full setup, covering the scoped key, every input and output, the Gerber path, and what each failure message means, is in the EMI analysis in CI documentation.
Comparing two versions of a board
The most reliable output from this tool is a comparison rather than an absolute figure, and it is worth explaining why.
An absolute number, such as "your board emits 42 dBµV/m at 3 metres", is not trustworthy here, and the tool does not present one as though it were. Too much is unmodelled: the components are not in the model, neither are the cables, the stackup permittivity is an estimate, and the mesh staircases every curve.
A comparison between two runs of the same board is a different matter. Almost all of those errors are systematic, so they largely cancel between the two runs. A statement like "this layout radiates 8 dB less than that one at 480 MHz, and this is the trace responsible" holds up despite every limitation listed below, because both runs are wrong in the same direction and by a similar amount.
That is the main argument for running this in CI rather than opening the tool occasionally. CI reliably has both versions available, the base branch and the change, and can compare them without anyone having to remember to. A pull-request comment reading "this change raised the 480 MHz harmonic by 6 dB" is more useful than any absolute figure the tool could produce, and it is a claim the method supports.
Limitations
Each of the following is a property of the method rather than a gap we expect to close.
- No whole-board full-wave solves. A typical four-layer board is roughly 1.9 billion mesh cells, which is not tractable at any reasonable cost. Region-of-interest solving is the only workable approach.
- Cable radiation is not visible. Most real EMC failures below about 300 MHz come from common-mode current on an attached harness. A board-only model does not include the harness, so it cannot see this.
- The useful band is roughly 100 MHz to 3 GHz. Below that, simulated time scales as
3/f_minwhile the timestep stays pinned by the smallest copper feature, so the run gets much longer without producing more insight. - Components are not modelled. ICs, connectors and decoupling capacitors are treated as copper geometry only. At GHz frequencies package parasitics dominate, and they are not represented.
- Stackup accuracy is the user's responsibility. Permittivity and loss tangent are frequency- and fab-dependent. An incorrect ε_r shifts every resonance in the result.
- This is not a compliance prediction. A CISPR limit line is drawn on the chart for context only. It is not a pass, it does not replace a test house, and the tool does not report compliance.
Taken together, these mean the tool answers which of two layouts is better and why, not whether a board will pass certification. It is useful on every commit when used the first way, and misleading when used the second.
Where the compute runs
The solve tier is resource-intensive, so the workers that run it dial in the same way BenchPod and build agents do: outbound only, with no inbound port to open and no firewall exception. A worker can be a DigitalOcean droplet, an office workstation, or a laptop for development. On registration it advertises what it can handle: cores, RAM, and the largest mesh it will accept.
That last figure matters because EMI runs vary enormously in size, spanning about four orders of magnitude, where build jobs are roughly uniform. A run whose estimated cell count exceeds every online worker is refused up front with a cost estimate, rather than accepted and left to fail hours later having occupied a machine. The estimate covers cells, RAM, timesteps and expected runtime, and you see it before committing to the run.
Runs are only ever dispatched to workers in your own organisation.
Getting started
Open Tools → EMI Analyzer and upload a .kicad_pcb or a project zip. Within a few seconds you get your copper rendered in the browser with layer and net selection, plus the rules findings. No solver is involved, and there is nothing to install or configure.
Starting there is worthwhile before wiring anything into CI. Run it against a board you know well and see whether the findings tell you something you did not already know, which is a quick way to judge whether it is worth adding to your pipeline. Once it is in CI, the checks run on the layout commits themselves, so the feedback arrives while the routing decision is still recent rather than after a fab order has been placed.