The Command Line
circuitRF runs without the GUI — not just its engines, but authoring, validation, resolution and drawing too. One executable, fifteen verbs — S-parameters, DC, harmonic balance, loadpull, loadpull pursuit, electromagnetic extraction, layout interchange, creating a workspace or a cell, importing a part, rendering a document as a picture, checking a design, explaining what it resolved to, reading a result back, an elaborated-netlist dump, and an MCP server. Every one of them answers --json. This chapter is the operational reference for all of them, including a worked EM run and a worked render, each from an empty folder.
Invoking it
The command-line driver is the same program as the GUI's Run button with the window taken off. It
reads the same files, elaborates them with the same elaborator, runs the same engines, and evaluates
the test bench's measure lines with the same evaluator.
$ circuitrf <verb> <file> [options]
The installed circuitRF executable is the command line. There is no second program to install: when its first argument is a verb, circuitRF runs that verb and exits without opening a window; with no arguments, or with a document to open, it starts the application as it always has. Where that executable is depends on the platform — and the verb must be the FIRST argument, since that is how circuitRF tells a command line from a document to open:
| Platform | Installed as | On your PATH |
|---|---|---|
Windows, per-user installer (…-win-x64-user.msi) |
%LOCALAPPDATA%\Programs\circuitRF\circuitRF.exe |
yes — type circuitrf |
Windows, per-machine installer (…-x64.msi) |
C:\Program Files\circuitRF\circuitRF.exe |
yes — type circuitrf |
| macOS | /Applications/circuitRF.app/Contents/MacOS/circuitRF |
no — see below |
Linux, .deb |
/opt/circuitrf/circuitRF, linked as /usr/bin/circuitrf |
yes |
Linux, .tar.gz + install.sh |
linked as ~/.local/bin/circuitrf |
yes, if ~/.local/bin is |
circuitRF.com is what you are typing
Beside circuitRF.exe the installer puts a small circuitRF.com, and both
installers add that folder to PATH (the user's for per-user, the system's for
per-machine; a terminal opened before the install does not see it yet). Windows tries
.com before .exe, so a circuitrf typed in cmd or PowerShell
reaches the console-subsystem .com, which gives the command a console and makes the shell
wait for it. Shortcuts and double-clicked documents still open circuitRF.exe. A program
that starts circuitRF with its own pipes — an MCP client, a script, CI — can use either.
On macOS, run the full path, or link it onto your PATH once:
$ sudo ln -s /Applications/circuitRF.app/Contents/MacOS/circuitRF /usr/local/bin/circuitrf
circuitrf --version prints the version of the build that answered, and nothing else — the quickest
way to confirm which one is on your PATH.
From a source checkout there is no installed executable, so put dotnet run --project src/Cli --
wherever circuitrf appears:
$ dotnet run --project src/Cli -- sparam mycircuit.cnl --freq 1GHz:3GHz:50MHz
Run it with no arguments for the built-in help.
This is the point of the command line being the same code rather than a second
implementation. A .cnl that runs here runs when you open it in the workspace, and an EM
setup run with em writes the byte-identical Touchstone the Simulate button writes.
There is one elaborator, one set of engines, one measurement evaluator and one results-path
convention behind both.
The verbs at a glance
| Verb | Takes | Runs | Writes |
|---|---|---|---|
sparam |
.cnl or .csch |
The linear S-parameter engine over a frequency sweep | A Touchstone .sNp, always |
dc |
.cnl or .csch |
The nonlinear DC engine, or the sweep that wraps it | Node voltages, probe currents and measurements, to stdout; the cubes with -o |
hb |
.cnl or .csch |
Harmonic balance, single- or multi-tone | Spectra tables to stdout; -o .mat/.npy/.txt |
lp |
.cnl or .csch |
Loadpull over the directive's Γ grid | A per-Γ-point table; -o .mat/.npy/.txt/.spl/.lpcwave |
lpp |
.cnl or .csch |
Loadpull pursuit — searches for the optima | Optima + the follow-on grid; -o as hb; --out-grid writes a .gam |
env |
.cnl or .csch |
The circuit envelope: a modulated signal through the circuit | Each IProbe's power, gain, efficiency and ACLR to stdout; -o .mat/.npy/.txt for the waveforms and spectra |
fields |
.cnl or .csch |
A fields analysis: an EM Field Data block's saved results recombined by the circuit, with no EM run |
The far field's metrics, or SAR and B1, to stdout; -o .mat/.npy/.txt for the pattern |
em |
.cem, or a .c3d with --setup |
The EM kernel or 3D solver the setup resolves to | A Touchstone .sNp, a grouped .npy and a .cemr result record, where Simulate writes them |
solver list, install, remove |
nothing — a tool name | The install assistant Settings ▸ Solvers runs | What each 3D solver's row shows, to stdout; an install or a removal, only with --yes |
opt |
.csch or .cnl |
The schematic's saved optimization — the Optimizer's own run | The best values and each goal's result to stdout; nothing in the design (--save-preset adds a preset) |
yield mc, yield estimate, yield trial |
.csch or .cnl |
A Monte Carlo or yield run over the tolerances on the tune lines, the kit's statistics and the statistics line |
The yield, each goal's result and the spread to stdout; the trials to <design>.yield.npy; nothing in the design (--save-preset, --save-corner with --trial) |
yield corners |
.csch or .cnl |
The design at every enabled corner, or a Monte Carlo at each (--mc) |
A corner × goal margin table to stdout; the corners to <design>.corners.npy; nothing in the design (--generate … --write on a .csch) |
yield center |
.csch or .cnl |
Design centering — the Yield panel's Centering run | The verified start and centred yields and the centred values to stdout; the verification to <design>.yield.npy; nothing in the design (--save-preset adds a preset) |
yield doe |
.csch or .cnl |
A design of experiments over the optimizer's or the tolerances' values | Each response's effects to stdout; the runs to <design>.doe.npy; nothing in the design |
yield sens |
.csch or .cnl |
The Sensitivity panel's ranking — each value stepped up and down on its own | Per goal, the values that move it most and their shares, to stdout; the steps to <design>.sens.npy; nothing in the design |
model new, import, deembed, fit, check-charge, explain, export, validate, report |
a .cmc |
The Model Composer's steps — the same functions its window calls | The .cmc, its cache, a .cmodel or a cell, a report folder — as the noun says |
rail |
.crail |
The same DC solve and via check railRF's Run button calls | The ports, the ranked breakdown and the via check to stdout; -o .csv/.npy/.mat/.txt/.svg/.pdf |
chain |
.cchain |
The same budget the RF Chain Budget window recomputes on every edit, and its Transfer to Schematic | The cascade to stdout; -o .npy/.mat/.txt for the budget as a DataSet; --to-schematic writes the test benches (and an array's Steering and Combiners benches) and their displays |
smith |
.csmith |
The same cascade evaluator the Smith Chart window walks on every edit | The reading and the per-node table to stdout; -o .s1p for the load Γ, -o .svg/.pdf/.png for the chart |
snp |
a Touchstone .sNp |
The same reading and plots the S-Parameter Viewer shows | What the file states, to stdout; -o .svg/.pdf/.png for the four plots, -o .cdd for a Data Display of them |
lvs |
a cell folder, a workspace, a .clay or a .csch |
The same comparison the LVS panel's Compare button calls | Nothing — the report to stdout; -o report.txt |
recognize |
a .clay, a cell folder, or a workspace with --cell |
Reads the board's copper as a circuit — ground, vias, ports, parts and lines as native components | Nothing — the report and the parts table to stdout; -o circuit.cnl, --into new:<name> for a schematic, --parts-out parts.csv |
impedance |
a .clay or a cell folder |
The same analysis the layout editor's Impedance Analysis runs | The per-trace report to stdout; -o report.pdf |
review |
a .clay, a cell folder, or a workspace with --cell |
The Layout Review's built-in checks | Nothing — the findings, grouped per check and layer, to stdout; -o writes the review as a PDF |
convert |
any layout format | The same importer and exporter File ▸ Import/Export runs | The layout in the format you asked for |
new workspace |
a directory | The same code File ▸ New Workspace runs | A .cws and, unless you say otherwise, a copied technology |
new cell |
a workspace + a name | The same code New Cell runs | A cell folder and one empty-but-valid file per view |
import part |
a component file or folder | The same code Import Component runs | A cell folder holding the land patterns and the symbol |
import model |
a library model folder | The same code Copy to Workspace… runs | A cell folder: the model as an ordinary, editable cell |
sign |
a model library folder | The same signing check and the library rows verify with |
library.sums and library.sig in the library; --new-key writes a key file |
import schematic |
a .kicad_sch, EDIF 2.0.0 or binary .DSN design |
The same code File ▸ Import ▸ Schematic… runs | A cell folder with its schematic, and a folder of symbol cells beside it |
render |
a .csch, .csym, .clay or .cdd, a cell folder, or a workspace |
The same Skia renderers the editors draw every frame with | A .svg, .pdf or .png, where -o says |
check |
a workspace, a cell folder, or one document | Every validator the application already uses | Nothing — findings to stdout |
explain |
the same | Resolution only — no analysis | Nothing — the walk and the answer, to stdout |
read |
a result file, or one of circuitRF's own documents | The same loaders the Data Display reads a file with | Nothing — what the file holds, to stdout |
netlist |
a .csch, a cell folder, or a workspace — or a .cnl with --to-schematic |
The same extraction Simulate performs, or that extraction run backwards | A .cnl, or the netlist text to stdout; a drawn .csch with --to-schematic |
plot |
a result file | Builds a one-plot data display and draws it | A .svg, .pdf or .png, where -o says |
find |
a directory | Nothing — it reads documents | Nothing — the workspaces, cells, views and analyses under it |
reference |
nothing | Nothing — it reads no file | Nothing — the reference pages, and every netlist primitive with its terminals and parameters |
elab |
.cnl or .csch |
Elaboration only, no analysis | The elaborated netlist, to stdout |
plugin list, describe, run, deny, install, uninstall, update, rollback |
a plugin's id (or, to install, a folder, a link or a list id) | The same code Tools ▸ Plugins and Settings ▸ Plugins run | run: each view as a file with -o, or a placed cell with --place; the others manage what is installed |
serve |
--root <dir> |
An MCP server for an external client | Whatever the tool it is asked for writes |
hb, lp and lpp all run the whole parametric sweep when one wraps the analysis — see
naming the wrapper.
Every run verb takes a schematic as well as a netlist. Hand it a .csch and it extracts the
netlist in memory first — the same extraction Simulate performs — so you do not have to write one
out to run a design you drew. netlist is how you see what it will run.
Results on stdout, everything else on stderr
stdout is the result. stderr is everything else — progress, per-grid-point engine chatter,
[circuitRF] notes, elaboration and engine warnings, device-worker logs.
That split is what makes the output pipeable while the terminal still shows a long run moving:
$ circuitrf lp hero3.cnl > table.txt
table.txt gets the loadpull table and nothing else; the per-drive-step [LP] lines and the
convergence notes still scroll past on screen. Redirect 2>/dev/null to silence them, or
2>run.log to keep them.
Options every verb takes
| Option | What it does |
|---|---|
--kits <dir> |
A folder of installed kits, so an externally-supplied device model (ExtDevice Provider=…) resolves headlessly the way opening a workspace resolves it in the GUI. Repeatable. |
--trust-kit <dir> |
Lets this run execute the PCell scripts of the kit whose generator manifest is in <dir>, so a layout's generated cells can be rebuilt when its .generated-cells folder is missing. A kit you have already allowed in circuitRF on this computer needs no flag. Repeatable. |
--json |
Put one JSON document on stdout and nothing else — see below. stderr is untouched. |
--only a,b |
Narrow that document's result to these cubes. |
--group g,h |
Narrow that document's result to these groups. |
--at axis=value |
Narrow it to one point of an axis — --at freq=2GHz. The nearest grid point, and the document says which one it gave you. |
--interp |
Make every --at interpolate between the two bracketing points instead. Never the default: it returns a number the run did not compute, and the document says so. |
--range axis=lo:hi |
Keep a band of an axis — --range freq=1GHz:3GHz. |
--result full\|summary |
summary returns the result's shape — group and cube names, units, axis lengths and extents — and no values at all. |
--summary |
Report the informational notes as counts by severity instead of in full. Warnings and errors always travel in full, and stderr is untouched. |
Frequencies are written as 1GHz, 100MHz, or bare Hz (1e9) anywhere a frequency is accepted.
--only and --group narrow by cube name, which does nothing when the
result has one cube. A 551-point two-port S-parameter run is about 173 kB of JSON; if the
question is "what is S21 at 2 GHz", --at freq=2GHz --only S is a few
hundred bytes. Every value carries its own unit — a bare 2 could be 2 Hz or
2 GHz — and an axis name nothing in the result has is refused, listing the ones that exist,
rather than quietly handing you everything.
Every run returns result.shape whether or not it returns the values, so
--result summary is how you find out what a run produced before deciding
what to ask for.
Every verb stops with unknown option '…' and exit 1 rather than dropping a flag it
does not recognise. This matters more than it sounds: most verbs find their input file as
the first argument that is not an option, so a silently dropped flag's value would be
read as the file name — and a flag that carries an override, like
--set, would simply not be applied, giving you a run that answers a
different question with nothing to say so.
sparam — S-parameters
$ circuitrf sparam <file.cnl|.csch> [--freq start:stop:step] [--set var=expr] [-o out.sNp]
$ circuitrf sparam hero1.cnl --freq 1GHz:3GHz:1GHz -o hero1.s2p
S-parameter analysis: 3 points, 1–3 GHz
Wrote hero1.s2p
| Option | What it does |
|---|---|
--freq start:stop:step |
Override the sweep. Omit it and the netlist's own sparam analysis is used, segments and all — which is almost always what you want, because it is the sweep the design was set up with. |
--set <var=expr> |
Override a global variable before elaboration, as hb does. Repeatable. Everything written in terms of it re-derives, including the netlist's own sweep. |
-o, --output <path> |
Where the result goes, and its extension picks the format: .s1p….s99p for a Touchstone, or .npy / .mat / .ds / .txt for the cubes. Omitted, it is the input file with its extension changed to .sNp for the port count found. |
There is no stdout table. The port count in the default extension comes from the network, so a
circuit that grew a port writes .s3p without you editing the command. An extension naming no format
this verb writes is refused, listing the ones it does — you never get a Touchstone under a name that
says otherwise.
A run carrying WSProbes prints one line per probe after the S summary, and a run
with NDF=yes on its directive prints the right-half-plane pole count:
$ circuitrf sparam amp.cnl
S-parameter analysis 'SP1': 2001 points, 0.5-3 GHz (1 segment(s))
NDF: 2 right-half-plane pole(s) (net clockwise encirclement 1.989; NDF(100 GHz)=1 ∠ 1.8)
WSProbe P idx=1 H0(0.5 GHz)=11.597 ∠ -1.4 ZG(0.5 GHz)=10.482 ∠ 17.4
SM_Y0 min -18.1 dB @ 1.59125 GHz SM_H0 min -19.8 dB @ 1.73375 GHz
--json carries the same under wsprobes and ndf. The margins are linear there and dB on the
line, because dB is a display convention and a document should carry the number. A circuit that has
WSProbes and no ports at all is a legitimate run — it writes every wsp cube and no S — and
asking it for a Touchstone is refused, naming the spellings that do carry the result.
A Touchstone file declares one reference impedance, and circuitRF writes port 1's on
the option line. When the ports differ, the file also carries a header note listing each port's own
impedance and saying that the data is referenced to those — and circuitRF reads that note back,
so circuitrf read on the file reports the real per-port references rather than the
option line repeated. Nothing is renormalized: the numbers are the ones the solve produced. If you
want the per-port references in a form every tool reads, write .npy instead.
dc — the operating point
$ circuitrf dc <file.cnl|.csch> [--set var=expr] [-o out.{npy,mat,txt}]
$ circuitrf dc hero2.cnl
DC: converged in 3 iteration(s), residual 7.27E-16
Node voltages:
0 0
n_src 0
n_gate -3.05
n_drain 48
--set var=expr overrides a global before elaboration, as hb does; it is repeatable. It
prints the converged node voltages, any probe currents and the netlist's measure lines — a
measurement names the result by the DC analysis that declares it (DC1.V("n_drain")), as it does in
the window — and
exits 2 if the solve did not converge — the operating point is the one thing every nonlinear
analysis is built on, so a non-converged DC is a failed run, not a partial one. -o writes the result
cubes and the measurements as .npy, .mat, .ds or .txt.
A parametric_sweep that wraps the DC analysis runs as the sweep, as it does for sparam, hb, lp
and lpp: an I–V family is a DC inside two sweeps, and running the DC alone would return one point. The
sweep's name goes to stderr with the count of points that did not converge; stdout carries the
measurements over the whole family, and -o writes the cubes with one axis per sweep. A swept run
exits 2 if any point did not converge.
$ circuitrf dc family.cnl -o family.npy
DC sweep 'SWD': 21 point(s) over Vds
DC: converged at all 504 point(s)
Measurements:
Id [Vds:21 x Vgs:24]
...
Wrote family.npy
with the sweeps in the netlist, the inner one stepping Vgs at each Vds:
analysis DC1 type=dc
analysis SWG type=parametric_sweep Var=Vgs Start=-5 Stop=0.75 Step=0.25 Inner=DC1
analysis SWD type=parametric_sweep Var=Vds Start=0 Stop=20 Step=1 Inner=SWG
measure Id = DC1.I("ID")
hb — harmonic balance
$ circuitrf hb <file.cnl|.csch> [-a name] [--set var=expr] [-o out.npy]
The same verb runs single- and multi-tone — which it is comes from the netlist's directive, not from a flag.
$ circuitrf hb hero2.cnl --rows 6
HB 'HB1': f0=2 GHz, MaxHarm=4, tol=1E-06
Analysis: HB1 (hero2.cnl)
Converged: yes (1 solve(s))
Residual: 1.24E-09 (worst)
Tones: 2 GHz
V [node:7 x harmonic:5] (mag ∠deg)
0 1 2
n_gate 3.05 ∠ 180.0 0.029814 ∠ 0.1 0
n_drain 48 ∠ 0.0 0.15004 ∠ -172.4 1.6824E-05 ∠ -179.8
… 1 more row(s) — use --all or --rows N
| Option | What it does |
|---|---|
-a, --analysis <name> |
Which analysis to run. Optional when the file declares one HB chain. |
--set <var=expr> |
Override a global variable before elaboration. Repeatable. |
--maxharm K |
Override MaxHarm. |
--maxmix M |
Override MaxMixOrder (multi-tone only). |
--tol t, --max-iter N |
Override the convergence tolerance and the iteration cap. |
--rows N, --all |
How much of each printed table to show. Default is a truncated head. |
--diag |
Engine convergence diagnostics, on stderr. |
-o, --export <path> |
Export the results. The extension picks the format: .mat, .npy, .ds or .txt. |
--set overrides the VARIABLE, not the number
--set Pavl_dbm=0 replaces the global variable in the test bench's own scope, then elaborates. So
every expression derived from it re-derives — a bias that was written Vg = Vth + 0.2 follows a
changed Vth, and a sweep computed from the variable sweeps the new values.
An override pushed at the engine instead would move one number and leave everything computed from it stale, which is why there is no such option.
Name the wrapper, or name nothing
When a parametric sweep wraps an analysis, the sweep is what
runs. Naming the inner analysis with -a is promoted to its outermost enabled wrapper, and the
promotion is announced:
[circuitRF] 'HB1' is the inner analysis of 'SW1' — running 'SW1' so the sweep axis is not lost.
Running the inner analysis alone produces a converged, plausible, complete-looking result at one operating point — with the sweep axis silently missing. Nothing about it looks wrong. A frequency-swept loadpull has exactly this shape, which is why the rule is the same for every verb rather than something harmonic balance does on its own.
If more than one runnable chain exists, all their names are printed and the first runs; if none does, the message says whether the netlist declares no such analysis or declares one that is disabled.
Measurements
The measure lines on the test bench are evaluated exactly as the GUI evaluates them, and the results
join the exported DataSet as named cubes. A measurement that fails to evaluate is reported on
stderr and the run continues — one bad expression does not throw away a run that took minutes:
[circuitRF] measurement: Measurement 'Gain_dB': failed to evaluate 'Pout_dBm - Pavl_dbm':
Unresolved name 'Pout_dBm' in scope 'measurements'
lp — loadpull
$ circuitrf lp <file.cnl|.csch> [--grid grid.gam] [--pin start:step:max] [-o out.spl]
lp sweeps the load (or source) termination over the directive's Γ grid, runs a harmonic-balance
drive ladder at each point, and reports the figures of merit.
$ circuitrf lp hero3.cnl --rows 8
Analysis: LP1 (hero3.cnl)
Grid: 20 point(s) — 0 reached compression, 20 stopped at max drive
Nothing reached compression — raise --pin's max (or the directive's PinMax).
# GammaLoad ZLoad (ohm) stop Pavl Pout Gt DE% PAE%
0 0.0000 ∠ 0.0 50.00+j0.00 max drive 10.00 20.54 10.54 3.35 3.09
1 0.2000 ∠ 0.0 75.00+j0.00 max drive 10.00 22.31 12.31 5.03 4.76
2 0.2000 ∠ 90.0 46.15+j19.23 max drive 10.00 20.19 10.19 3.09 2.83
… 12 more point(s) — use --all or --rows N
| Option | What it does |
|---|---|
-a, --analysis <name> |
Which loadpull analysis to run. |
--set <var=expr> |
Override a global variable before elaboration. Repeatable. |
--grid <file.gam> |
Override the Γ grid the directive reads. Resolved against your working directory, not the netlist's. |
--pin start:step:max |
Override the drive ladder, in dBm. |
--compression dB |
Override the compression target. |
--maxharm K, --tol t, --max-iter N |
Override the inner HB settings. |
--rows N, --all |
--all dumps every cube instead of the summary table. |
--diag |
Engine diagnostics, on stderr. |
-o, --export <path> |
.mat, .npy, .ds, .txt — or .spl / .lpcwave, the loadpull interchange formats. |
One row per Γ point, at the point that answers the question
A loadpull's raw cubes are [gridPoint × driveStep] — a 61-point grid driven up in 1 dB steps is a
61 × 30 table per figure of merit, and eight of those scroll a terminal without answering anything.
So the default table is one row per Γ grid point: where it was, how it stopped, and its FOMs at
the last converged, non-tickle drive step — the compression point where the point compressed, the
highest drive it managed otherwise. Reading a fixed drive index instead would mix compressed and
uncompressed points in one column. --all still dumps everything.
A swept run prints one table per sweep point.
.spl and .lpcwave
-o out.spl writes the loadpull interchange format the Data Display reads back
as a measured surface, so a headless run can produce a file the GUI opens. lp also runs the same
post-processor a GUI run does, so the exported cubes carry the derived display metrics (Pout_dBm,
Zin, IRL_dB, AMPM_deg) — a .npy written here and one written by the GUI carry the same cubes.
lpp — loadpull pursuit
$ circuitrf lpp <file.cnl|.csch> [--out-grid found.gam] [-o out.npy]
A pursuit searches for the max-power (MXP) and max-efficiency (MXE) terminations rather than reading a grid, then runs a follow-on loadpull over the terminations it recommends.
$ circuitrf lpp hero3B_at_compression.cnl
Analysis: LP1 (hero3B_at_compression.cnl)
Pursuit optima:
MXP (max power) converged Pout=40.625 dBm Zload=80.48+j0.00 Zsource=50.00+j0.00
MXE (max efficiency) converged Eff=69.617 % Zload=140.31-j4.95 Zsource=50.00+j0.00
21 termination(s) queried, 45 recommended termination(s)
Grid: 45 point(s) — 45 reached compression
# GammaLoad ZLoad (ohm) stop Pavl Pout Gt DE% PAE%
0 0.2690 ∠ 7.0 86.15+j6.11 compressed 26.00 40.56 14.56 67.08 64.74
1 0.2030 ∠ 9.6 74.80+j5.30 compressed 27.00 40.68 13.68 63.55 60.82
lpp takes every lp option except --grid, and adds --out-grid:
| Option | What it does |
|---|---|
--out-grid <file.gam> |
Where the terminations the pursuit found are written, as a .gam you can feed back to lp. Resolved against your working directory. |
--grid on lpp and --out-grid on lp each stop the
run with a sentence naming the verb that owns them. A grid option silently doing nothing would be a
run that answered a different question and said nothing about it.
A non-converged optimum is still printed, with its status. The engine publishes the last termination it looked at, and printing nothing there reads as "the search found nothing" when what actually happened is "nothing it tried reached compression".
env — the circuit envelope
$ circuitrf env <design.cnl|.csch> [-a name] [--set var=expr] [--stop 200e-6] [--step auto]
[--accuracy draft|standard|fine] [--envelope auto|single|lattice]
[--segments auto|<n>] [--warmup auto|<s>] [-o out.{mat,npy,txt}]
Runs the design's type=env analysis, and the whole sweep when a parametric_sweep wraps it. A MOD_Source supplies the
signal. Every labeled net records its voltage, and every IProbe the power delivered through it.
What a run records lists every cube. Minutes, not seconds: 200 µs of an NR 20 MHz signal is
about a minute on 10 cores.
To plot the power spectrum at an IProbe, write the cubes and plot PSD. Its branch axis is named after the IProbes, and it is
already in dBm/Hz, so it takes y=real:
$ circuitrf env amp.csch --stop 100e-6 -o amp.npy
$ circuitrf plot amp.npy -o spectrum.svg --freq-unit MHz --ylabel "dBm/Hz" --legend inside \
--trace 'cube=PSD["Iout",:],y=real,label=Iout' --trace 'cube=PSD["Iin",:],y=real,label=Iin'
Exit 0 when it ran, 1 refused or failed, 130 cancelled.
fields — far field, SAR and B1 from a schematic
$ circuitrf fields <bench.cnl|.csch> [-a name]... [--set var=expr]... [-o out.{mat,npy,txt}]
Runs every enabled type=fields analysis, or the ones -a names. Each reads an EM Field Data block's saved
per-port results and recombines them with the waves the circuit delivers to its ports, so it starts no EM run and a part changed
with --set re-runs in seconds. A far field gives F1.farfield.U, Etheta, Ephi and every antenna metric. A field set gives SAR and
B1 at one frequency. Either gives the circuit's F1.S and F1.Z0.
A pattern stays out of the --json document for its size, so write it with -o and plot that file. A pattern is a polar plot in
decibels, one cut per bearing:
$ circuitrf fields "Antenna Bench.csch" -a F1 -o f1.npy
$ circuitrf plot f1.npy -o pattern.svg --type polar --radial db --db-unit dBi --whole-plane --legend below \
--trace "cube=F1.farfield.U,cut=0,y=db10,label=phi 0" --trace "cube=F1.farfield.U,cut=90,y=db10,label=phi 90"
$ circuitrf plot f1.npy -o surface.svg --type surface --view iso --trace "cube=F1.farfield.U,cut=all,y=db10"
Plot flags has the rest. Exit 0 when every fields analysis ran, 1 refused or failed, 130 cancelled.
trl — fixture files from TRL standards
$ circuitrf trl <bench.cnl|.csch> [-a name]... [--set var=expr]... [--fixture-a path] [--fixture-b path] [-o out.{mat,npy,txt}]
Runs every enabled type=trl analysis, or the ones -a names (TRL Calibration), and writes each one's
two fixture files where its directive says. --fixture-a and --fixture-b replace those paths for one analysis. -o
exports the result (SA, SB, LineDeg, LineLossDb, Gamma, Usable) and the bench's measurements; it does not move
the fixtures. Under --json
both fixture files are listed in outputs.
Exit 0 when it ran, a usable-band warning included, 1 refused or failed, 130 cancelled.
osc — a free-running oscillator
$ circuitrf osc <design.cnl|.csch> [-a name] [--set var=expr]... [--fguess f] [--maxharm k] [--tol t] [--aguess a] [--probe OP1] [--phase-noise] [-o out.{mat,npy,txt}]
Runs the design's oscillator analysis, or the one -a names: the start-up search, then
harmonic balance with the frequency as an unknown. A parametric sweep wrapping it runs the whole sweep, so a tuning curve
is one command. --fguess (in the directive's Unit), --maxharm, --tol, --aguess and --probe replace the
directive's keys, and --phase-noise turns on its phase noise. The result is
Fosc, Aosc, Startup and the harmonic-balance cubes at the oscillation, with the design's measurements beside them,
and PN, AN, PNc, PNpct and PNcorner with phase noise.
Exit 0 when the oscillation was found, 1 refused (the circuit does not start, or has no OscProbe) or failed, 130 cancelled.
em — electromagnetic extraction
$ circuitrf em <setup.cem> [-o out.sNp] [--workspace file.cws]
em is the only verb that does not take a .cnl. It takes a .cem EM setup — the document the
EM Setup panel edits — and runs it: extracts the geometry from the layout the setup
names, resolves the stackup, meshes, solves the frequency plan, de-embeds, and writes the results.
It needs no other arguments. Everything else it needs is already recorded in the files.
--device auto|cpu|gpu chooses where a 3D run solves. A Palace run solves on the CPU unless --device gpu says
otherwise; that needs a GPU build of Palace, and Palace on a GPU has the details. An
EMerge run is auto unless --device says otherwise, whatever Settings ▸ Solvers holds: the GPU sparse solver
for a driven problem large enough to be faster on it (EMerge on a GPU), the CPU
otherwise.
--device gpu is refused on a run that uses neither Palace nor EMerge.
--cores <n> is Settings ▸ Solvers ▸ Cores for this run: how many Palace processes or solver threads it may use. A run that
does not say uses one process per physical core, and solver list says how many this machine has. Each Palace
process holds its own share of the mesh and its own solver data, so more processes take more memory and fewer take less, more
slowly. The memory estimate in --dry-run and explain does not follow the count, so when a run stops at its memory limit, lower
--cores before coarsening the mesh. explain --cores <n> and em --dry-run --cores <n> report the processes a run would start and warn
when that is more than this machine has, which a run refuses.
A 3D run's solver is stopped if its memory reaches this computer's (--memory-limit <GB> sets a lower limit), or if
the startup volume or the results' volume falls below 10 GB free; either way the run writes nothing and exits 1.
--force lifts the memory limit once the run's estimate is past this computer's memory (below that it keeps the limit), and never
lifts the disk floor. explain on a 3D setup reports both, and takes
--memory-limit too.
--adopt <file.sNp> solves nothing: it writes the .cemr record of a result from before records existed, beside its
Touchstone, and refuses when no .npy matches it. explain <file.cemr> reads a record back, and -o moves the Touchstone alone.
--mesh-only runs a 3D or thermal setup as far as its mesh and stops before any solver starts: it reports the elements and Gmsh's
warnings, or Gmsh's errors with the objects named and where they are, and writes no result. It exits 0 on a mesh, 1
on a refusal or a mesher failure, and 130 when cancelled. --dry-run also warns about geometry known to stop the
mesher, naming the object, its layer and the layout coordinates.
A 3D or thermal run makes its mesh in the Scratch folder that Settings ▸ Solvers names, the system temp folder
when none is named, and prints it as a Scratch: line beside the solver. --json carries it on every run, refused or
not, as result.scratch: folder, source (preference or system), exists, and systemTemp, which a solver's
own helpers still use (the parallel runtime's session files for a Palace run on several processes). A named folder
that is not there, such as a drive that is not connected, refuses the run before meshing and exits 1.
--component-ports "C1,C2" (or "*") runs a .cem or a .c3d's setup with those
component ports for this run only; nothing is written back to the setup. A part
that cannot be built is left out with a warning naming it, and the rest run. A thermal or electrostatic setup has no
ports and refuses the flag. With --dry-run it reports the ports a run would build and writes nothing.
A .c3d runs too — circuitrf em <view.c3d> --setup <name> — and a thermal setup embedded in it runs through the
same verb with circuitRF's own thermal solver (Gmsh meshes it). It writes <cell> <setup>.thermal.npy and the temperature
field beside it, and prints the probes, the measures and, when the setup asks for one, the Rth matrix. See
Thermal ▸ Headless.
What an EM run takes
Four files, and three of them are things you already have if you have drawn a layout:
| File | What it supplies | Where it comes from |
|---|---|---|
.cem |
The setup: which layout, which analysis, the frequency plan, port impedances and types, mesh settings, solver switches | File ▸ New ▸ EM Setup…, or the layout editor's EM button |
.clay |
The artwork — the metal, and the port labels for a full-wave run | The layout editor |
.ctech |
The stackup: layer thicknesses, ε_r, tanδ, conductivity, which conductor is ground, and which drawing layers map onto what | The technology editor, or one of the shipped starter technologies |
.cws |
The workspace marker, carrying DefaultTechRef — the technology a layout uses when it does not name one itself |
Created with the workspace |
The em verb runs a setup — it does not create or edit one, and it will not
repair one. A setup with no ports, no technology or no signal conductor is refused
with the sentence explaining what is missing. Build the .cem once in the
EM Setup panel, where every control tells you as you type whether the run
is blocked and why, then commit it beside the layout and run it headlessly from then on.
Both file references resolve by walking UP, and neither is a flag
A .cem names a layout; the layout names — or inherits — a technology. Neither reference is stored
absolutely, and neither needs an argument:
- The layout. The setup's layout reference is relative to the workspace root: the nearest
ancestor
.cwsfound by walking up from the.cem. With no workspace above it at all, the reference falls back to the.cem's own directory, so a loose.cemsitting beside its.claysimply works. - The technology. Resolved against the layout's own parent workspace, found by walking up from
the
.clay— never against "the workspace you are in", of which there is none headlessly. A.claythat names no technology picks up its workspace'sDefaultTechRef.
The two walks start from different files, and that is deliberate. A .cem in one workspace may
point at a layout in another, and that layout's layers have to be read by its technology, not by
whichever workspace the setup happened to live in.
--workspace <file.cws> overrides the first walk, for a .cem being run from outside its own tree.
It is never required.
The three resolutions are echoed on stderr before anything expensive starts, so you can see what the run is actually about to read:
[circuitRF] workspace: /work/amp/.cws
[circuitRF] layout: /work/amp/Line/layout/Line.clay
[circuitRF] technology: /work/amp/pcb.ctech
A worked example, from an empty folder
Here is a complete, minimal EM workspace — a single 20 mm × 2.9 mm microstrip line on a two-layer PCB technology, swept 1–10 GHz in 3 points. Four files:
amp/
├─ .cws the workspace marker, naming the default technology
├─ pcb.ctech the stackup
├─ line.cem the EM setup
└─ Line/
└─ layout/
└─ Line.clay the artwork
The .cem is JSON, and this is all of it — every field not written takes its documented default:
{
"FormatVersion": 1,
"Name": "line",
"LayoutRef": "Line/layout/Line.clay",
"Frequency": {
"StartExpr": "1", "StopExpr": "10", "NumPoints": 3,
"Mode": "PointCount", "Kind": "Linear",
"StartUnit": "GHz", "StopUnit": "GHz"
},
"Port1Z0Real": 50, "Port2Z0Real": 50
}
LayoutRef is workspace-relative — relative to the directory holding .cws, not to the .cem.
The .cws supplies the technology:
{ "DefaultTechRef": "pcb.ctech" }
Nothing in the .cem names a technology, a kernel, a mesh or a port. The technology is inherited, the
kernel is chosen from the geometry, the mesh settings are the engine's own defaults, and this
structure's ports are the two ends of a uniform line by construction. Then:
$ circuitrf em amp/line.cem
[circuitRF] workspace: amp/.cws
[circuitRF] layout: amp/Line/layout/Line.clay
[circuitRF] technology: amp/pcb.ctech
[0] solving the cross-section
[3] solving the cross-section
note: Automatic chose "Uniform transmission line": this geometry is a uniform cross-section, which
that analysis solves exactly and is about a thousand times cheaper than "Full-wave planar".
Set Analysis to "Full-wave planar" if you want the full-wave answer anyway.
note: Dielectric interfaces truncated 20 substrate heights (32000 µm) beyond the outermost conductor
on each side.
EM setup: line
Kernel: Quasi-static cross-section (CrossSection)
Points: 3
Wrote amp/results/line.s2p
Wrote amp/results/line_em.npy
Everything from EM setup: down is on stdout; the resolution lines, the progress and the notes are
on stderr.
A full-wave run differs only in what the files say, not in how you invoke it: draw port labels in
the layout with the layout editor's Port tool, set the setup's analysis to Planar (or leave it
Auto and let the geometry decide), and run exactly the same command. It will take very much longer —
a de-embedded full-wave point costs tens of seconds at the shipping mesh — which is why the progress
lines exist.
Where the results go, and what -o moves
With no -o, the run writes exactly where the Simulate button writes: into the workspace's
results/ folder. Two files come out, and they are not redundant:
| File | Holds |
|---|---|
<name>.sNp |
S-parameters only — the artefact a schematic's SnP component references by path |
<name>_em.npy |
The whole DataSet, including the per-kernel diagnostics group — Z_c, γ, ε_eff, RLGC for the cross-section kernel; the calibration residual and usability flags for the full-wave one |
That results path is predictable by design, so a schematic's SnP reference stays valid across
re-runs. A headless run that minted its own file name would orphan every one of them — so
circuitrf em writes the same file Simulate does, and the acceptance test for the
verb compares the two Touchstones byte for byte.
-o moves the Touchstone only. The .npy stays where it was, because it is the diagnostics
record of the run rather than the deliverable:
$ circuitrf em amp/line.cem -o /tmp/mine.s2p
Wrote /tmp/mine.s2p
Wrote amp/results/line_em.npy
You do not have to get the extension right — the port count decides it, so a .s2p you typed for a
structure that turned out to have four ports is written .s4p.
With no workspace above the .cem, results/ is created beside the .cem itself.
Comparing solvers: --solver and --reference
A 3D setup's solver can be changed for one run without editing the file: --solver palace, openems or
emerge runs that solver alone, and a comma list compares them.
circuitrf em via.cem --solver palace,emerge # EMerge against Palace
circuitrf em via.cem --solver openems,emerge --reference emerge # openEMS against EMerge
circuitrf em via.cem --solver compare # the setup's own Compare set
circuitrf em via.cem --solver both # Palace and openEMS
The solvers run one after the other, Palace first, then openEMS, then EMerge. The reference is Palace when
it is listed, otherwise the first. Each solver's result lands where it would alone, and the comparison in
results/<name>.compare_em.npy (EM Setup ▸ Compare describes what it holds). A
solver that is not installed, or cannot solve the setup, is refused before anything runs, naming it and
why.
One component's drawn part: --component
$ circuitrf em mydie --component "SPIRAL N=2.5 W=10 um S=10 um Din=100 um" --freq 1GHz:20GHz:1GHz -o L1.s2p
Extracts one built-in component as it would be drawn: its layout cell on the workspace's technology,
with a port on every terminal — port 1 is terminal 1, and so on — so the Touchstone can replace the
part in the schematic as an SnP. The line is the type and its parameters,
written as on a .cnl instance line. The path is the workspace (or its .cws, or a .ctech); with
none, the current folder's workspace is used. -o is required; with no --freq the sweep is 1–20 GHz.
This is how to get a value to rely on for a spiral inductor, whose built-in inductance is an estimate. The extraction meshes two cells across the narrowest metal, with no edge mesh, so that a coil fits the planar solver — the run says so, and the Q it reads is low. A thin-film resistor is refused: its film is a sheet resistance, not a layer the solver meshes, and its own model is exact for what it states.
note, warning, error — three lists, kept apart
An EM run has three different things to say and they ask three different things of you, so they are printed under three labels rather than flattened into one stream:
| Prefix | Means |
|---|---|
note: |
The run explaining itself — which kernel it chose and why, the mesh's own sentences, RLGC, the ports it found. Read these; they are the cheapest check that the tool is looking at the structure you think it is. |
warning: |
Something to act on — a stale .sNp about to be replaced, a technology that resolved but failed validation. |
error: |
Something you asked for and did not get — a results file that could not be written. |
A 3D view's terminal ports on Palace
A .c3d whose wave port has two terminals runs on Palace too when its lines' modes travel at different speeds
(EM Setup ▸ On Palace), and the run's notes say how its terminal S was made: one line per port
naming its terminals as the modes of one Palace wave port and how they were converted, then one giving each terminal's
power and the singular values of S.
note: Port 'Left': terminals P1, P2 as modes 1 and 2 of one Palace wave port, converted to terminal S (modes not
degenerate, Robin correction K = 1.079 to 1.086).
note: Terminal S checks: per-port power |S_jj|² + Σ|S_ij|² … to …, singular values … to …, reciprocity max |S_ij − S_ji| …
An adaptive sweep adds a note naming the frequencies each terminal face's modes were solved at. A fit with no consistent
root, a lossless problem whose S is not lossless, a terminal voltage that does not give Palace's mode impedance, and a
face supporting a mode past its terminals' own are each a warning:, with what to do about it. Palace's modal S, the
voltages, the mode impedances and wavenumbers, and the fitted G and K are kept in the run's .npy (the palace group's
modal_* cubes), so a disputed result can be re-derived without running again. Three terminals on one face, a
reference plane moved off the face, or a lumped port beside them are refused before anything runs, naming openEMS; a face
whose modes travel at one speed (a stripline) is refused once the mesh exists, by its own mode solve, before the 3D solve.
A refusal is a result
The EM engine declines geometry it cannot solve correctly rather than returning a plausible number.
Each refusal carries a written explanation of what is wrong with this setup, and em prints that
explanation rather than collapsing it into "EM failed":
[circuitRF] workspace: amp/.cws
warning: Layout file not found: amp/Line/layout/Missing.clay
No layout: The layout 'Line/layout/Missing.clay' could not be found, so there is no geometry to
analyse. Point this EM setup at a layout that exists.
| Status | Means | Exit |
|---|---|---|
| Refused | The extractor or the kernel declined this geometry — see what the engine refuses | 1 |
| No layout | The layout reference did not resolve | 1 |
| Engine error | The solve failed | 1 |
| Cancelled | Stopped at a work boundary | 130 |
solver — installing, listing and removing the 3D solvers
$ circuitrf solver list [--capability gpu]
$ circuitrf solver install <palace|gmsh|openems|emerge> [--version <v>] [--gpu] [--yes]
$ circuitrf solver remove <palace|gmsh|openems|emerge> [--version <v>] [--yes]
$ circuitrf solver remove --all [--yes]
The install assistant of Settings ▸ Solvers, with no window: the same recipes, the same install record, the same removal. A build machine installs a solver this way.
listprints what each Settings row shows: found or not, the version and whether circuitRF has validated or tested it, where it is and how it was found, and the command that would install it here. EMerge's line adds the sparse solver it would use (fast sparse solver, or the single-threaded baseline with what to install) and whether its GPU sparse solver is installed.--capability gpualso asks Palace whether it solves on a GPU, which costs a one-element solve. Read-only: it installs and builds nothing.listends with this machine's physical and logical cores and its memory (machinein--json), which is whatem --coresis chosen against.listadds anupdate:line for an EMerge circuitRF installed when it has tested a newer one (updatein--json).solver install emerge --yesinstalls it beside the older one, and new runs use it.install <tool>prints what it would fetch, from where, where it would go and what it cost when measured, and exits 1 having fetched nothing.--yesagrees and installs.circuitrf solver install emerge --yesinstalls EMerge into a Python of its own;--gpualso fetches a temporary Rust toolchain into EMerge's folder to build its GPU sparse solver, and deletes the toolchain after. See Getting the most speed from EMerge.remove <tool>removes a copy circuitRF installed, after printing the space it frees;--allremoves every one. Again nothing happens without--yes. A program circuitRF did not install is never removed.
Exit 0 when installed (or already installed) or removed, 1 when refused or failed, with the report on stderr, 130 when cancelled.
opt — optimizing from the command line
circuitrf opt <schematic.csch | netlist.cnl> [flags]
opt runs the optimization a schematic saves — its tuned values' ranges, its goals and its algorithm,
as the Optimizer panel sets them up — and reports what it found. It is the
panel's own run, not a second one: the same schematic and the same seed give the same best values,
cost and simulation count headless as they do in the window. On the Tuning and Optimization example:
$ circuitrf opt LSectionMatch/schematic/LSectionMatch.csch
Optimization: lm (LSectionMatch/schematic/LSectionMatch.csch)
Finished: every enabled goal is met
Best cost: 0 (12 iterations, 39 evaluations)
Variables:
key start best min max railed
L1.L 5 nH 12.7087915391915 nH 1 nH 50 nH
C1.C 0.5 pF 1.48508255273602 pF 0.1 pF 10 pF
Goals:
name met value at margin
Match yes -20 freq = 1.05E+09 0
Each best value is the text the schematic would hold, at full precision; a part of a complex value is followed by the whole value, in the form the schematic writes it. A met goal reports its tightest point and the margin there; an unmet one its worst point, with a negative margin.
A vendor part (tune C1.Part … opt=1) is reported by part:
its row's best is the chosen part number and its min and max are the candidates' value span, and a
Parts table lists each entry's start, the chosen part, its value and description. With other
variables, the snap line gives the cost with ideal components in the parts' place and the cost with the
real parts, the difference the parts' parasitics make. The --json document carries the same as parts
(the chosen part and the vendorlib:// reference of its file, which is what the instance's Name and
File are written as) and snap.idealParts.
| Flag | |
|---|---|
--algorithm, --max-iter, --max-evals, --time, --cost lsq\|minimax, --analyses goals\|all, --parallel, --seed |
Override the saved settings, for this run only. |
--vars key,key, --goals name,name |
Optimize only these of the saved variables, against only these goals. A complex value is named by its parts (mag(Zs)), never whole. |
--corners all\|none\|name,name |
Meet the goals at these corners as well as the nominal, overriding the saved corners=. Each point then costs one simulation per corner, and each goal's result names the corner that binds it. |
--snap |
Snap and polish at the end. |
--sensitivity |
A sensitivity pass at the best point. |
--show-iterations |
Every iteration as well, one line each on stderr (and in --json). The default is the final result only. |
--save-preset <name> |
Add the best values to the schematic as a preset. The one thing opt writes; refused for a .cnl, where the preset is a line you add. |
-o out.npy |
The best point's results, and the run's history in an opt group. |
--set var=expr |
As for every run verb. |
It never changes the design's values. To put the best point in the schematic, push it in the
panel, recall a saved preset, or write the values into the file. An exit code of 3 means the run
finished with at least one goal unmet — not a failure of the run, but not a design that meets its
specification either. Over MCP the same run is run analysis=optimize, and
reference goals, reference tuning and reference optimizers say what may be written;
reference statistics covers tolerances, correlations, the statistics settings and corners.
yield — Monte Carlo and yield from the command line
circuitrf yield mc|estimate|trial|corners|center|doe|sens <schematic.csch | netlist.cnl> [flags]
yield draws the design's tolerances — the dist= keys on its tune lines, and its kit's process and
mismatch statistics — and simulates each draw. mc reports the spread alone; estimate
counts how many trials pass the goals marked use=yield or use=both and reports that yield with its
confidence interval; trial re-runs one trial and says what it drew. Every trial is a function of
the seed and its own number, so the same file and seed give the same trials on any machine, at any
parallelism.
$ circuitrf yield estimate div.cnl --target 80%
Yield estimate: div.cnl
seed 1 · sampling random · 200 of 200 trials · 200 trials
Yield: 88.0 % (95 % interval 82.7 % – 92.2 %; 176 of 200 counted trials pass) · target 80.0 %: met
Goals:
name yield interval worst margin trial
Vout 88.0 % 82.7 % – 92.2 % -0.01022 58
Statistics over the evaluated trials:
of n mean sigma min max median Cpk
goal:Vout:margin 200 0.004593 0.004064 -0.01022 0.009956 0.005223 0.3767
Vo 200 0.5003 0.006768 0.4817 0.5202 0.5005
Worst trials for Vout:
trial 58 margin -0.01022 R1.R=979.902516568072 Ohm R2.R=1062.49587500816 Ohm
...
A progress line per batch goes to stderr (-q silences it). The trials are written to
<design>.yield.npy beside the design: every analysis result with an outer trial axis, each trial's
drawn values, margins and pass/fail, the nominal, and a yield summary. read prints that summary
first, and plot draws the statistics functions directly — --trace "cube=histogram(trials.goal:Vout:worst, 20)".
| Flag | |
|---|---|
--trials, --seed, --sampling random\|lhs\|sobol, --confidence p%, --nonconverged fail\|warn, --save scalars\|all\|n\|auto, --parallel, --analyses goals\|all |
Override the statistics line, for this run only. |
--target p%, --autostop |
estimate only: the yield to meet, and stopping as soon as the interval is clear of it either way. |
--process 0\|1, --mismatch 0\|1, --sigma-scale k |
The kit's process and mismatch draws, and a scale on every kit sigma. |
--vars key,key, --goals name,name |
Draw only these tolerances (the rest stay at nominal); score only these goals. |
--trial n |
Re-run only trial n. -o out.npy then writes its results. yield trial scores it as the design's own run would — a yield when there is a yield goal, a Monte Carlo otherwise. |
--save-preset <name> --trial n |
Add that trial's values to the schematic as a preset. Refused for a .cnl. |
--save-corner <name> --trial n |
Add a corner naming that trial, so later runs can replay it. Refused for a .cnl, and with --vars, --sigma-scale, --process, --mismatch or --set: a corner replays the trial under the design's own settings, so it would not be the trial you saw. |
--contributions |
Which tolerances drive each goal's and measurement's spread, largest first. The table shows each share between 0 and 100 %; --json gives the fitted share as it stands, which can pass 100 % or fall below 0 when tolerances are correlated. |
-o out.npy |
Where the trials are written. |
--set var=expr |
As for every run verb. |
A flag that a run would not use is refused, naming the runs that take it — --trials on yield corners without
--mc, say, or --confidence on yield doe. reference statistics lists every flag with the runs that take it.
It never changes the design's values. Exit 0 means the run finished and the yield met
--target (or there was none); 3, that it finished below the target; 1, that it was refused;
2, that no trial evaluated; 130, that it was cancelled, with nothing written. Over
MCP the same runs are run analysis=montecarlo, run analysis=yield and run analysis=corners, and
reference statistics says how to write tolerances, yield specs and the statistics line.
explain <file> --analysis reports how wide the interval of the configured trial count will be and
roughly how long the run takes, before you start it.
Corners
yield corners checks the design at every enabled corner line — a kit corner selection, a temperature, values —
in one run, and prints one row per corner with each goal's margin:
$ circuitrf yield corners div.cnl
Corners: div.cnl
a goal FAILS at a corner
corner temp Vout margin status
nominal 0.08 passes
cold -40 0.1572 passes
hot 125 -0.002045 ✗ FAILS
hiR2 85 0.05158 passes
Worst corner per goal:
Vout: hot (margin -0.002045, FAILS)
It exits 3 when a goal fails at any corner, and writes <design>.corners.npy. A corner made from a Monte Carlo
trial (--save-corner) replays that trial's draws around the design's current values, so it follows the design as
you tune it.
| Flag | |
|---|---|
--corners name,name |
Only these corners. On mc and estimate, a run at each of them. |
--mc |
A Monte Carlo — a yield, when the design has a yield goal — at each corner, with the kit's process draws off there: the corner is the process. The table is then a yield per corner. |
--generate "spec" |
Print the corners a cross product makes and write nothing, e.g. "proc=tt,ss;temp=-40,25,85;Vdd=3.0,3.6" — a kit axis by its name, temp, any variable or tunable value, and part:<part number>=min,max for a placed library part's own corners. With --json, each corner's name, temperature and values (a part's corner as part:<part number>). |
--write |
With --generate, on a schematic: add the generated corners to it. |
$ circuitrf yield corners div.cnl --generate "temp=-40,25,85;R2.R=900 Ohm,1100 Ohm"
corner tm40_R2R900 temp=-40 R2.R=900 Ohm
corner tm40_R2R1100 temp=-40 R2.R=1100 Ohm
corner t25_R2R900 temp=25 R2.R=900 Ohm
...
Centering
yield center moves the values marked opt=1, within their ranges, to where the most trials meet the specs, and
then checks the result: the starting values and the centred ones are each run on the same set of fresh trials. On
the Yield, Corners and Centering example:
$ circuitrf yield center DividerCentering/schematic/DividerCentering.csch
Design centering: DividerCentering/schematic/DividerCentering.csch
cmaes · 100 common trials (seed 1) · 10 iterations · 4100 simulations · the iteration limit (10) was reached
Verified: yield 73 % [70.1 %, 75.7 %] → 99.1 % [98.3 %, 99.6 %] on the same 1000 fresh trials; the intervals do not overlap · target 95.0 %: met
...
The verified pair is the result; the search's own yields, on its common trials, are shown beside it and labelled as
such. The flags are the center line's, for this run only — --algorithm, --trials (the common trials each
position is scored on), --verify, --max-iter, --max-evals, --time, --width, --parallel, --seed — with
--target, --confidence, --sampling, the kit switches, --vars and --goals.
| Flag | |
|---|---|
--surrogate quadratic |
Score each position on a quadratic fit of the specs' margins instead of simulating every trial — 410 simulations instead of 4,100 on the divider above. The final check is still simulated. |
--save-preset <name> |
Add the centred values to the schematic as a preset. Refused for a .cnl. |
-o out.npy |
Where the verification of the centred values is written. |
Exit 0 means the verified yield met --target; 3, that it fell short; 1, refused; 2, no position
evaluated; 130, cancelled with nothing written. Over MCP it is run analysis=center.
Design of experiments
yield doe runs the design at a planned set of combinations of its values — the opt=1 values at the ends of
their ranges, or with --factors stat the toleranced values at their nominal ± k σ — and reports, for each spec and
measurement, each value's effect and each pair's interaction, largest first. An effect larger than the noise level
estimated from the small ones is starred; an effect a fractional design cannot tell apart from another lists it.
| Flag | |
|---|---|
--design full2\|frac\|pb\|ccf |
Full factorial, fractional factorial, Plackett–Burman screening, or a face-centred composite for a curved model. |
--resolution 4\|5 |
For frac. |
--factors opt\|stat, --levels range\|sigma:k |
Which values are the factors, and where their low and high ends are. |
--centre n |
Centre points added (0 adds none). |
--responses goals\|all |
Which goals are responses: the specs, or every enabled goal. Every scalar measurement is a response either way. |
--optimum |
Search the fitted model for the point that best meets the specs, and simulate that point to confirm it. |
It writes <design>.doe.npy and nothing in the design. There is no target, so the exit codes are 0, 1, 2
and 130. Over MCP it is run analysis=doe.
Sensitivity
yield sens runs the Sensitivity panel's ranking: each value is stepped up and down on its own
about the design as it stands — a toleranced value by ± k σ, any other by ± --step %, each S-parameter of a
Touchstone block by ± 0.1 dB and ± 1° — and each goal is given the values that move its normalized margin most,
largest first, with each one's share. That is 2k + 1 simulations for k values, and nothing is drawn at random.
$ circuitrf yield sens SnpSensitivity/schematic/SnpSensitivity.csch
Sensitivity: SnpSensitivity/schematic/SnpSensitivity.csch
factors=all · step 1 % · sigma 1 · 31 simulations
Gain m₀ 0.04807 σ 0.01945 (normalized margin: margin / scale)
rank entry factors Δ share
1 L1 · S21 2 0.01424 53.6 %
2 AMP · S21 2 0.01111 32.6 %
3 L1 · S11 2 0.004913 6.4 %
...
| Flag | |
|---|---|
--factors all\|stat\|opt |
Every value, only the toleranced ones, or only the opt=1 ones. |
--step p%, --sigma k |
How far a value with no tolerance moves, and how many σ a toleranced one moves. |
--ports 0\|1 |
With --factors all, also step the ports' reference impedances (left out by default). |
--snp on\|off, --snp-exclude a,b |
Step S-parameter blocks at all, and which to leave out (SNP4, DUT:SNP4). A block left out is listed as excluded, not as insensitive. |
--vars key,key, --goals name,name |
Step only these values; rank only these goals. |
--responses goals\|all |
The specs, or every enabled goal. Every scalar measurement is ranked either way. |
--by key\|entry\|port\|instance |
The level the table groups by (default entry); --json carries all four. |
--top n |
Rows per goal in the table (default 10); --json carries every one. |
Each flag overrides the file's sensitivity line for this run only. It writes <design>.sens.npy (-o moves it)
and nothing in the design. Exit 0 means it ran; 1, refused; 2, the nominal did not evaluate; 130,
cancelled. explain <file> --analysis gives the factor count and the cost before you run it. Over MCP it is
run analysis=sensitivity.
model — the Model Composer, headless
circuitrf model <noun> <doc.cmc> [flags]
Builds a FET model (.cmodel) or a passive cell from measured or simulated data, step by step, through the same
functions the Model Composer window calls — so a document worked on here and opened in the window holds the same
bytes. Each noun prints one line per item on stdout (--json gives the same lines as result.model.items, each with
what it is about), progress on stderr. The formats are in File Formats, and as
circuitrf reference cmc and reference cmodel.
| Noun | What it does |
|---|---|
new <doc.cmc> --kind active\|passive |
Creates the document. Active: --network a stock network (gan-two-shell, classic-shell, series-only, copied beside the document) or a six-pin .csch; --fingers N0 --unit-width W0 ("125 um"), the measured device's periphery. |
import <doc.cmc> <source…> --as <kind> |
Adds a data set — DcIv, PulsedIv, SParams, PulsedSParams, SimulatedTables, LargeSignal or PassiveSParams — and imports it. A folder is every file in it. --pattern reads a Touchstone set's bias from its file names (--pattern-source comment from its comments), and a passive document needs one only to read a family's {geometry}; --map says what a CSV column (Vg=vgs:V) or an array (vg=vgs, f=freq:GHz; in a .ds, SP1.S=S, with a leading - to read it negated: -DC1.VD.i=id) holds — for a .mat, .npz, .ds or .npy it may be left out, and the map the array names suggest is used and printed; --condition Key=Value sets a measurement condition (ReferencePlane=device, AmbientC=25, Quiescent.Vds=28); --name, --format, --no-header, --delimiter, --z0, --freq. --dry-run reads the sources and writes nothing, and needs no .cmc yet: what the file holds, the map, what would be read and how, and every warning (Model Data). |
deembed <doc.cmc> |
Removes the pads and the network: a line for each, then a line per data set of its intrinsic bias ranges. |
fit <doc.cmc> --step <step> |
network, thermal, current, traps, charge, passive, or all — every step the document needs, in order. The document's own settings, with --method (the step's method: MultiBias, ANN, Table, Equation, a trap model — with no pulsed I–V, written with the Shift coupling), --seed n and, for a passive document, --topology written into them first. To change anything else, edit the step's Settings in the .cmc and fit again. Only DC I–V is needed: with no S data, all holds the network at its values and the charge step writes the capacitances typed in its Settings.Typed (zero when none are). |
check-charge <doc.cmc> |
Checks the fitted charges conserve charge. Also <design.cnl> --device X1 --bias vgs,vds, for any device in a netlist (--region adds the capacitance check over a rectangle). Exit 3 when the spurious DC at the gate is above --threshold (default 0.1 % of the fundamental gate current). |
explain <doc.cmc> |
The data sets and their planes, the intrinsic ranges, N0 and W0, the temperature mode, each step as fresh, stale or absent, every network value with its error and whether it is poorly determined, and the levels each trap state was trained from, or the shift coupling's strengths. Fits nothing. |
export <doc.cmc> -o <out> |
Writes the model in the format -o's extension names: .cmodel, with --symbol naming the symbol, or .va (also .vams), Verilog-A source for another simulator, with --module naming the module (default: the file name). A .cmodel exports to .va too, with no document. A table constituent is refused, naming each. --created sets a .cmodel's timestamp; a .va records the model's own. A passive document exports as a cell: --cell <workspace> <name>. |
validate <doc.cmc> |
Compares the document's current model — or --model x.cmodel (or a .cmodel with --cmc doc.cmc) — with its data, at the measurement plane. --against set,set, --tolerance S95=0.02 (also SMedian, IvRelative, PowerDb, EfficiencyPoints, AmPmDegrees, ImpedanceVswr), --compression dB for the compression a loadpull is compared at. Exit 3 when a tolerance is missed. |
report <doc.cmc> |
Writes the Model Validation Report folder (-o, default <name>-report beside the document): report.npy, summary.txt and every testbench it ran — and its Data Display document, <folder>.cdd, beside the folder. --pdf out.pdf also draws every tab of it, one page each: the same file the window's Export PDF writes. --model, --sections, --classes B,J,F,FInverse, --freq, --bias vgs,vds, --source-2f0 re,im, --ropt, --disc radius,rings,spokes, --pin start,step,max and --compression override the options the document stores. |
A choice the window would ask you for is refused, naming the flag that answers it: a Touchstone import with no
--pattern, a fit of an Active document with no N0 (--fingers), an export with no -o.
$ circuitrf model new fet.cmc --kind active --network gan-two-shell --fingers 4 --unit-width "125 um"
$ circuitrf model import fet.cmc sparams --as SParams --pattern "Vgs{vgs}_Vds{vds}_Id{id:mA}" --condition ReferencePlane=device
$ circuitrf model import fet.cmc dc.csv --as DcIv --map Vgs=vgs:V --map Vds=vds:V --map Ig=ig:A --map Id=id:A
$ circuitrf model fit fet.cmc --step all --seed 1
$ circuitrf model export fet.cmc -o fet.cmodel
$ circuitrf model export fet.cmodel -o fet.va
Then place it — ComposedFET:X1 g d 0 File=fet.cmodel — and run it like any device. fet.va is the same model for
another simulator; --json adds result.model.verilogA: the path, the module, its parameter count and the switches
that change nothing for this model.
Exit 0 done; 1 refused; 3 finished, but a check failed; 130 cancelled, with nothing written. fit --step all keeps the steps that finished before a cancellation. Over MCP it is the model tool, one mode per noun.
rail — power integrity, headless
$ circuitrf rail <board.crail> [--rail NAME] [--accurate] [-o out.{csv,npy,mat,txt,svg,pdf}]
rail runs a .crail — the document the railRF window edits. It resolves the
artwork and the stackup, extracts the copper, solves the rail at DC, checks the vias and prints the
answer. Every number comes out of the same call the window's Run button makes, and every pixel of an
.svg or .pdf report out of the same renderer the window draws with, so a report produced on a build
machine is the one you would have got by pressing the button.
Omitting --rail runs them all, in dependency order — the same shape hb and lp have for a
wrapped sweep, and for the same reason: a downstream rail solved on its own would start its source from
a nominal instead of from the upstream answer.
It can also take the .clay, the cell folder or the workspace the .crail sits in, and find it.
What it reads, and what walks up to it
Like em, the references are walk-ups rather than flags. The .crail names its artwork; the
artwork names — or inherits from its workspace — a technology.
| File | What it supplies |
|---|---|
.crail |
The rails, their references and extents, the sources, the loads and their currents, the parts, the targets, the band and the aggressors |
.clay (or a Gerber set) |
The copper being measured |
.ctech |
The stackup: conductor thicknesses and conductivities, the dielectrics between them, and the via entry's plated-wall thickness |
.crlib |
The part library the decoupling resolves against |
Options
| Option | Meaning |
|---|---|
--rail <name> |
Which rail. Omitting it runs every one. |
--fast (default) / --accurate |
The two readings of the copper. Fast is the default, as in the window. |
--source REFDES.PIN=<model> |
Repeatable. 3.7V,50mOhm,10nH — any subset, in any order, each field identified by its unit or by a v=/r=/l= key — or a Touchstone file. A row for the same anchor is replaced, not added beside it. |
--load REFDES.PIN[=<current>] |
Repeatable. The current may be left out: that makes it an observation port. |
--target-drop, --target-z, --mask [PORT=]<file> |
The target forms. A mask is per observation port; one that lands on no port is refused, because a mask nobody applied reads on the report exactly like one that was honoured. |
--aggressor NAME=<freq>[xN] |
Repeatable. x and × both spell the harmonic count. |
--reference <layer>, --extent as-imported\|filled\|infinite |
The return conductor, and how far it is taken to extend. |
--rows N, --all |
How much of the ranked breakdown to print. |
-o out.… |
.csv for the tables, .npy/.mat/.txt through the usual exporter, .svg/.pdf for the report page. |
An anchor is REFDES, REFDES.PIN, or @x,y in DBU. Headless it is always the coordinate form:
a .crail names no placement file, so there is nothing to resolve a refdes against — see
what is not wired up yet.
Values carry units, through the same table the expression engine uses, so a
spelling that works in a .cnl works here. A bare number is base SI, which is what every number in a
.crail already is.
What is refused, and what is not
| Unstated | Answer |
|---|---|
| The reference layer | Refused, naming --reference. railRF never infers one. |
| The technology | Refused. Copper priced with no thickness and no conductivity produces numbers that look exactly like numbers with physics behind them. |
| The Excellon coordinate format, on a Gerber import | Refused — the same sentence convert gives. |
| The via plating thickness | Not refused. It is a setting, and every flag says which basis produced its limit. |
| A load's current | Not refused. It is an observation port, it contributes nothing to the DC solve, and the report lists it as observed. Refusing it — or defaulting it to zero — would make not added and added with no current indistinguishable. |
-o out.sNp is refused: Z(f) at the observation ports is the frequency answer, this verb answers
DC, and a Touchstone holding the DC point repeated would look like a measurement. --set is refused
too, naming the flags that do state those quantities: a .crail declares no variables, so a --set
accepted here would be silently dropped.
Every export says which model produced it
A file read six months later has no status strip beside it, so every format carries the model (Fast or Accuracy), the reference extent, the copper temperature, how many parts are modelled from a file, how many have no bias curve, and whether any ESR fell back to a class default — which makes a derived peak height indicative rather than measured.
lvs — does the artwork implement the drawing?
$ circuitrf lvs <path> [--no-reduce] [--flat] [--flatten-cell NAME] [--testbench]
[--recognize] [--set var=expr] [--severity warning|error] [-o report.txt]
lvs compares a cell's layout against its schematic and reports every device, net, terminal and
value the two disagree about. It answers the one question a headless client cannot answer any other way:
the design was drawn twice, and do the two drawings say the same thing? An agent that authored a .clay
cannot look at the screen.
Every finding comes out of the same call the LVS panel's Compare button makes, so a design that passes on a build machine passes when somebody opens it.
The path may be a cell folder (the default unit — its primary schematic against its primary
layout), a workspace (every cell holding both views), a .clay or a .csch (each finds its
sibling in the cell folder that holds it). A cell holding only one of the two views is reported and
skipped, not failed: that is the ordinary state of a design being drawn.
| Option | Meaning |
|---|---|
--no-reduce |
Compare object for object. By default parallel and series R/C/L collapse first, and every finding un-reduces to the objects you drew. |
--flat / --flatten-cell <name> |
Flatten the whole hierarchy, or one named sub-cell (repeatable). Every use is reported, so a design that quietly flattens everything is visible. |
--testbench |
Compare a bench as drawn rather than the cell it instantiates. |
--recognize |
Also read devices out of bare copper, through the technology's own DeviceRules deck. Off by default, and off for a process that declares no rules. |
--set var=expr |
Set a global before the schematic elaborates, exactly as a run verb does. |
--severity warning\|error |
What makes the exit code non-zero. Default error. |
-o report.txt |
The only thing this verb ever writes. With no -o it writes nothing at all, so it runs on a read-only tree and on a workspace another process has open. |
Exit 0 when nothing at or above --severity was found, 1 otherwise, 130 on a cancellation.
A run holding warnings and no errors exits 0 and still reports every one.
With --json, every finding travels with a stable lvs. id and typed arguments — the layer, the
coordinate, the two values, the marker box — so a caller reads what it needs without parsing the
sentence apart. It honours waivers and never creates one: waiving is a reasoned act with a sentence
attached and belongs beside the thing being waived.
It is not folded into check, deliberately. check has to stay cheap enough to call after
every edit; an LVS on a real board is seconds rather than milliseconds, and a check that had become
slow is a check people stop running. What check does carry is the
terminal-map validation, which is cheap and static.
See the LVS chapter for what the findings mean and a worked example on the shipped example workspace.
recognize — a board's artwork as a circuit
$ circuitrf recognize <file.clay | cell folder | workspace --cell NAME>
[-o circuit.cnl] [--into new:NAME | --into artwork] [--replace]
[--parts-out parts.csv] [--parts parts.csv] [--bom FILE] [--placement FILE]
[--placement-origin symbol|body|pin1] [--placement-unit mm|mil|in]
[--region x0,y0,x1,y1] [--ground NET | --ground-at x,y] [--vias model|ground]
[--coplanar auto|microstrip|gcpw] [--coplanar-factor K] [--start F] [--stop F] [--npts N]
[--digits N] [--free-orientation] [--arrange board|tidy]
recognize reads a board's copper and writes the circuit it implements: the ground, the vias that matter,
the ports, every two-terminal part as an R, L, C or S-parameter file, and every trace as an MLIN (with its
bends, tees, crosses and tapers), CPWG, SLIN or TLIN, with an S-parameter analysis ready to run. A part
whose value is not known becomes a global variable with a tuning range, so it is a knob in the Tuning and
Optimizer panels from the first simulation.
With no -o and no --into it writes nothing: it prints what it found, one line per kind of finding,
and the parts table as CSV. Review that table before writing anything:
$ circuitrf recognize Board --parts-out parts.csv
# edit parts.csv: C6's Value to 10pF, L2's Kind to L …
$ circuitrf recognize Board --parts parts.csv --into new:Board_model
$ circuitrf sparam Board_model/schematic/Board_model.csch -o board.s2p
| Option | Meaning |
|---|---|
-o circuit.cnl |
Write the circuit as a netlist. Nothing else. |
--into new:NAME / --into artwork |
Write the schematic into a new cell beside the artwork's, or into the artwork's own cell (only when that cell has no schematic yet). |
--replace |
Replace a schematic this command wrote earlier; a history checkpoint is taken first. A schematic you drew yourself is never replaced. |
--parts-out / --parts |
Write the parts table as CSV; read an edited one back. A value you give replaces the variable that stood for it. |
--bom, --placement |
A bill of materials and a placement file. A placement file that does not say which origin it was exported against needs --placement-origin. |
--region x0,y0,x1,y1 |
Read only this rectangle; a line it cuts becomes a port. Every coordinate carries a unit (um, mm, mil) — a bare number is refused. |
--ground NET, --ground-at x,y |
Read this net, or the copper at this point, as ground. |
--vias model\|ground |
Keep vias to ground as VIAGND (default), or make them plain grounds. |
--coplanar auto\|microstrip\|gcpw, --coplanar-factor K |
How a line with ground close beside it is read. |
--start, --stop, --npts |
The analysis range, each frequency with its unit. Default: the layout's EM setup's sweep, else 100 MHz – 6 GHz in 201 points. |
--digits N |
The significant digits every number in the circuit is written with, 1 to 15. Default 6. |
--free-orientation |
Draws every shunt part below its line. Without it, a shunt part is drawn on the side of its line that its copper is on — the app's Link symbol and footprint orientation setting. Applies to --arrange tidy. |
--arrange board\|tidy |
How the schematic is laid out. board (the default) puts each part roughly where it sits on the board, so the drawing can be checked side by side with the layout. tidy draws the signal path as one line from port to port with everything else hanging from it. The circuit is the same either way. |
Exit 0 when the board was read (and written, when asked), 1 when it was refused — no technology,
no copper in the region, no port — with nothing written, 130 on a cancellation. With --json the
result carries every finding with its count and where it is on the board, and the parts table row for row.
explain on a schematic this command wrote shows where it came from — the layout, the region and the
options — and explain --ref resolves the layout's path.
A plot digitised by hand
$ circuitrf recognize plot.png --manual-box x0px,y0px,x1px,y1px[@deg]
--x-range LOW..HIGH[:log] --y-range LOW..HIGH[:log] [--y2-range LOW..HIGH[:log]]
[--curve "NAME=px,py;px,py;…[,axis=right][,is=S21:db]"] [--follow NAME=px,py]
[--sample N | --sample-step STEP] [--method linear|smooth] --csv out.csv [-o out.s2p]
The dialog's Digitise by hand from the command line. Nothing is read off the picture: the box is the plot area and the ranges are the values at its edges.
$ circuitrf recognize s21.png --manual-box 228px,147px,671px,443px \
--x-range 1GHz..5GHz --y-range -36..0 --follow S21=560,193 --csv s21.csv
| Option | Meaning |
|---|---|
--manual-box x0px,y0px,x1px,y1px[@deg] |
Two opposite corners of the plot area, in pixels, px on every number. @3 turns the box 3° counter-clockwise, for a scan that is not square. |
--x-range, --y-range, --y2-range |
The values at the box's left and right, and bottom and top, edges. An x range is frequencies with units (100MHz..10GHz), or plain numbers for an x that is not frequency. --y2-range is a second scale on the right. :log marks a logarithmic axis. |
--curve "NAME=px,py;…" |
A curve's points in pixels. ,axis=right reads it on the right-hand scale; ,is=S21:db makes it an S-parameter quantity, otherwise it is data. Repeatable. |
--follow NAME=px,py |
One click on a curve: it is followed by its colour inside the box and its points placed, one every 20 pixels and at every peak and dip. |
--sample N, --sample-step STEP |
Each curve sampled at N x values (101) from its first point to its last, or every STEP. |
--method linear\|smooth |
Straight between points, or a curve through them that never overshoots. Nothing is sampled beyond a curve's points. |
--csv out.csv, -o out.s2p |
The sampled curves as CSV; a Touchstone of the curves is= names, on a frequency x. |
A box given without both ranges is refused, naming the one missing. Exit 0 when the CSV (and the Touchstone, when asked) was written, 1 when refused.
impedance — every trace against a target Z0
$ circuitrf impedance <layout> [--target 50] [--tol 10] [--warn 20] [--max-freq 6GHz]
[--layers "Top Copper,Inner 2"] [--max-width <um>] [--via-transition 400um] [--width "Top Copper=457"]… [--no-scope]
[--region [<layer>@]x0,y0,x1,y1]… [--net <name>]… [--pick <layer>@<x>,<y>[:connected]]…
[--whole-layer <layer>]…
[--survey] [--severity warning|fail] [--ignore-accepted] [-o report.pdf [--report full|summary]]
impedance is the layout editor's Impedance Analysis: it finds
every trace on the chosen copper layers, cuts it along its length with the same quasi-static
cross-section solve the canvas's Trace Impedance uses, and reports per trace Z0 min, max and
average, the share of its length inside target ± tolerance, its line type, and every finding — Z0
out of band, a return path that breaks, copper only partly under it, a reference that steps to another
layer. -o writes the same PDF the dialog exports.
The path is a .clay or a cell folder holding one. The technology resolves exactly as the editor
resolves it, and placed cells are flattened as a design-rule check flattens them.
The review saved in the layout applies by default, so a headless run reports what the editor reports: the target, bands, frequency, layers, trace widths and regions, picks and nets last chosen in the editor's Impedance panel. A flag overrides the saved value, and the saved value overrides the default. A saved layer the technology no longer has is skipped with a line on stderr.
| Option | Meaning |
|---|---|
--target <ohms> |
The target Z0. Default 50. 50, 50ohm and 50R all read as 50 Ω. |
--tol <percent> |
± this many percent passes. Default 10. |
--warn <percent> |
± this many percent is a warning rather than a fail. Default 20; it must be wider than --tol, so a --tol of 20 or more needs a wider --warn too. |
--max-freq <freq> |
The highest frequency the traces carry. A stretch outside the warning band shorter than λ/20 there warns rather than fails — see electrically short — and a test pad hanging off a trace is judged there. The unit is required (6GHz); a bare number is refused. Default off. |
--severity warning\|fail |
What decides the exit code. Default fail; warning makes a warning exit 1 too. Warnings are reported either way. |
--layers "A,B" |
The copper layers, by the technology's layer names. Default every copper layer. A name that is not a copper layer is refused with the names that are. |
--max-width <um> |
The widest copper read as a trace. Default ten times the distance to the nearest other copper layer, between 1 and 8 mm. |
--via-transition <len> |
Trace within this distance of the land of a via on it is not checked, because a plane is normally cleared round a via; each skipped stretch is listed in its trace's notes. A bare number is µm. Default the saved setting, else 400 µm; 0 checks every trace up to its via. |
--width <layer>=<w>[,<w>…] |
Review only the traces of these widths on that layer; repeat it for another layer. A bare width is µm, as --max-width reads it, or give a unit (18mil, 0.457mm). It replaces the saved widths for that layer for this run; a layer with no widths is reviewed at every width. A trace's width is the width over most of its length, and a width matches within 1 % (or 1 µm). |
--region [<layer>@]x0,y0,x1,y1 |
Review the traces any part of whose centre line lies inside this rectangle, whole — on that layer only when one is named (Bottom@10mm,5mm,30mm,20mm), on every layer when none is. Every coordinate carries its unit (10mm,5mm,30mm,20mm, 400um, 50mil) and a bare number is refused, as for render --window: a layout coordinate could be DBU, µm or mm. explain --extents prints a layout's window in exactly this spelling. Repeatable. |
--net <name> |
Review the traces on copper carrying this net. Repeatable. |
--pick <layer>@<x>,<y>[:connected] |
Review the trace whose copper holds this point on that layer, coordinates with their units; :connected takes every trace joined to it through vias. A pick with no copper under it is reported on stderr and selects nothing. Repeatable. |
--whole-layer <layer> |
Review every trace on this layer while regions, nets or picks narrow the others. Repeatable. |
--no-scope |
Ignore the saved scope and review every trace. The saved target, bands and layers still apply. |
--ignore-accepted |
Report as if no finding had been accepted: every finding counts against its trace, and against the exit code. |
--survey |
List the width classes per layer — width, trace count, total length and one typical Z0 (a single cut at the middle of the class's longest trace) — and analyse nothing. The way to choose --width. |
-o report.pdf |
The PDF report. With no -o it writes nothing. |
--report full\|summary |
Which PDF -o writes: full (the default) lists every trace and finding; summary keeps the map and groups the findings, with the 20 worst in full — see the Summary. --json carries everything either way. With no --report, a Full report of more than 50 pages is still written, with a warning on stderr saying how many pages the Summary would take. |
Accepted findings apply by default. A finding accepted in the editor's panel is saved in the
.clay, and the verb reports it marked ✓ ACCEPTED with its reason and date; it does not count against
its trace's verdict or the exit code. Acceptances that matched nothing in this run are listed before the
closing line. There is deliberately no --accept: a run's trace ids (T1, T2…) are that run's,
and an acceptance is a decision made reading the finding. A script that must accept one headlessly adds
it to the .clay's ImpedanceAcceptances; reference layout documents the key —
the trace's two end points in whole µm and the finding's kind.
Regions, nets and picks choose; widths filter. A trace any --region, --net or --pick chooses
is reviewed (with none, every trace is), and --width then filters what they chose. With any of them
set, a layer none of them reaches is not reviewed — the scope line names it nothing selected
— unless --whole-layer keeps it. Each of the four replaces the saved selectors of its kind
for this run and leaves the other kinds as saved — so a lasso drawn in the editor, which has no
command-line spelling, still applies unless --region replaces it.
stdout starts with the target and, on the next line, the scope in words — Top Copper at 457 µm
(1 trace). 21 traces on Top Copper are outside the scope and were not analysed. — then one line per
trace — PASS, WARN or FAIL — its findings under it (! a fail, ? a warning), then
one line per junction — its kind and each arm's trace, width,
Z0 and result — and a closing line N pass, W warning, F fail, with the junctions' counts after it
when there are any. stderr says which layer is being analysed and, through a long solve, repeats the
line every few seconds with the cross-sections done, the time elapsed and an estimate of the time left on
that layer. Traces outside the
scope are not cut, solved or listed, only counted, and they still count as grounded copper beside the
traces under review. Lengths
and coordinates are in the layout's own unit. With --json the result carries every layer, trace and
finding, with coordinates and lengths in µm whatever the layout's unit, so a script reads one unit;
warningCount sits beside pass and fail, a trace's verdict can be "warning", and every finding
carries "severity": "warning" or "fail" — and an accepted one "accepted": { "reason", "date" }.
A test pad loading its line is a finding of kind "pad-shunt" whose shunt holds padLayer, diameter (µm),
planes, capacitance (the pad's, F), stubCapacitance (pad, via barrel and unused pads, F), inductance (the
via's, H; absent with no via), resonanceHz, s11Db, atHz and lineOhms.
Each layer's junctions lists its junctions (id, kind, verdict, at, and arms, each with
trace, width, z0 and verdict), and junctions at the top counts them; both are absent when there
are none. accepted counts the accepted findings and staleAcceptances lists the saved acceptances that matched
nothing (key, kind, layer, summary, reason, date). scope is the scope sentence and each layer's outOfScope
its count of traces left out. With --survey, the result is impedanceSurvey instead: per layer its
classes, each with width, widthMin, widthMax, traces, length (µm) and typicalZ0.
Exit codes follow check's convention, counting un-accepted findings only. 0 when
no trace or junction fails — warnings are always reported and still exit 0 — 1 when one fails (or no part of it could be solved) or the
run is refused, and with --severity warning when one warns; 130 on a cancellation. A cancelled run still writes the report for the layers that finished,
and says on its first page that it was cancelled.
The line calculator: impedance --tech
$ circuitrf impedance --tech <technology> --layer <name> (--width <w>[,<w>…] | --z0 <ohms>[,<ohms>…])
[--gap <g>] [--freq 10GHz]
With --tech and no layout, impedance answers for a line that is not drawn yet: the width a Z0
needs, or the Z0, εeff, loss and guided wavelength a width gives, on one copper layer of a
technology. Every row has two answers side by side:
- circuit model — what a line of that width on that layer simulates as: an MLIN, a
CPWG when
--gapis given, or an SLIN when the layer has a ground plane both above and below. It shows the substrate the layer resolves to, the static Z0 and εeff, and at--freqthe dispersive Z0 and εeff, the loss in dB/mm and λg. It is the same number a run uses. - cross-section (impedance) — what
impedancewould report on a line drawn at that width: the quasi-static Z0 and εeff, with λg from that εeff. The solve has no dispersion and no loss.
The difference column shows how far apart they are. A large gap is worth seeing before you draw: the schematic simulates the model, and the drawn line measures the cross-section.
--z0 gives each column its own width. The model width comes from the same synthesis the
parameter editor's Z0 field uses. The cross-section width is solved to 1 nm, the finest width a
layout can hold. A Z0 at the model's W row shows what a line drawn at the model's width would measure.
| Option | Meaning |
|---|---|
--tech <t> |
A .ctech file; a workspace folder, or a document inside one (it resolves the technology a schematic there would use); or the id of a technology that ships with circuitRF (reference technologies lists them). |
--layer <name> |
One copper layer: a drawing layer's name or a stackup conductor's. A name the technology does not have is refused with the names it does have. |
--width <w>[,<w>…] |
Widths to analyse, one row each. A bare number is µm, or give a unit (18mil). |
--z0 <ohms>[,<ohms>…] |
Impedances to find a width for, one row each. |
--gap <g> |
Makes the line coplanar, with ground on the same layer this far from each edge; the model is then a CPWG. On a layer with a ground plane both above and below it the line is a stripline with coplanar ground, and the cross-section is the only answer, because no circuit component models one. |
--freq <freq> |
Where the dispersive values, the loss and λg are given, with its unit (10GHz). Without it, only static values are shown. |
A layout path given with --tech, or a flag that only means something for a drawn layout (--target,
--region, -o and so on), is refused rather than ignored. With --json the result is
impedanceLine: the substrate and, per row, model (widthUm, z0Static, eeffStatic, z0, eeff,
lossDbPerMm, lambdaGUm), crossSection (widthUm, z0, eeff, lambdaGUm, configuration),
z0DifferencePercent and, on a --z0 row, crossSectionAtModelWidth and widthDifferencePercent.
Exit 0 when every row is answered, and 1 when the calculator is refused or a row has no answer.
A column that cannot answer one row is a warning.
review — a reviewer's checklist over the board
$ circuitrf review <layout> [--cell <name>] [--checks id,id] [--skip id,id]
[--set <check>.<threshold>=<value>]… [--defaults] [--ignore-accepted] [--as pcb|mmic] [--all]
[--severity warning|none] [-o report.pdf]
$ circuitrf review [<layout>] [--as pcb|mmic] --list-checks
review runs the Layout Review: the built-in checks a person
reviewing a board runs by eye, each marking the places worth a look with a sentence saying why. It is
not a design-rule check and does not replace one: a finding is advice, and the reviewer either changes
the artwork or accepts the finding with a reason.
The path is a .clay, a cell folder holding one, or a workspace with --cell. The technology
resolves as the editor resolves it, and placed cells are flattened as a design-rule check flattens them.
The board is read once and every check shares that one copy.
A review is of a board or of a die. A PCB review runs the checks below marked neither MMIC only; a MMIC
review runs ground-pin-via, coupled-lines, probe-pads and impedance. The layout's saved choice applies — a board when it has
none — and --as overrides it for this run.
| Check | What it marks | Threshold (default) |
|---|---|---|
via-in-pad |
A via inside an SMD pad's soldermask opening. Vias in a thermal pad are left to thermal-vias, and a pad centred on its one plated drill is a through-hole pin, whose drill is not a via. |
— |
ground-via-distance |
A ground pad with no ground via within the distance, pad edge to barrel edge; the nearest one's distance is stated, looked for out to ten times the threshold. Thermal pads and through-hole pins are not judged. | max-distance (0.5 mm) |
shared-ground-via |
A ground via that is the only one within ground-via-distance.max-distance of two or more ground pads. |
— |
thermal-vias |
A thermal pad — a ground pad of at least thermal-pad-area — with fewer ground vias under it than one per max-via-pitch squared, or none; a finding states the pad's average pitch. A via counts when at least half its barrel is inside the pad. thermal-pad-area is the thermal-pad size all four pad checks use. |
thermal-pad-area (2 mm²), max-via-pitch (1 mm) |
stacked-traces |
Two traces on adjacent copper layers, on different nets, running the same way on top of each other for at least min-length, each grown by lateral-margin first; a crossing is not a run. Layers with another copper layer between are not compared. A trace is what impedance finds. |
lateral-margin (0), min-length (1.0 mm) |
coupled-lines |
Two traces of different nets on one copper layer running the same way, closer than spacing for at least min-length, both in substrate heights (h: the dielectric between the layer and its ground reference). Reported with the run's length and the closest gap. PCB and MMIC. |
spacing (2 h), min-length (5 h) |
edge-copper |
Copper on any copper layer closer to the board edge than the pullback. The board edge is read off the board-outline layer — a closed outline, or a line drawn round the board — and cut-outs in the board are edges too. | pullback (0.25 mm) |
edge-trace |
A trace on any copper layer closer to the board edge than min-distance. Copper that is not a trace is edge-copper's alone. |
min-distance (1.0 mm) |
edge-stitching |
Within band-width of the edge: a stretch at least max-gap long with no ground copper on the top or bottom copper (one finding per layer), and a gap of at least max-gap between ground vias (a finding on no layer). Measured along the middle of the band. |
band-width (1.0 mm), max-gap (2.0 mm) |
ground-pin-via |
MMIC only. A placed part's terminal whose copper is ground with no via to the ground plane within max-distance, terminal to the via's edge; the nearest one's distance is stated, looked for out to ten times the threshold. |
max-distance (100 µm) |
probe-pads |
MMIC only. The placed GSGPADs: one whose pitch is not probe-pitch (the lab's probe, within 0.5 µm), a ground pad not on ground, and metal within approach in front of the pads on their metal or any above it. A pad inside another cell or in an array is not judged. |
probe-pitch (150 µm), approach (100 µm) |
impedance |
Each trace in the layout's saved Impedance review scope that does not pass it — FAIL, WARN or unsolved — with its verdict, worst Z0 against the target band, line type and finding kinds, marked where its findings are. Its line also says the scope and how many traces passed. With no scope saved it is not run: the review never analyses every trace on the board. | None of its own: target, bands, layers and scope are the Impedance review's |
The review saved in the layout applies by default — which checks run, and each threshold that
was changed from its default. --set overrides one for this run; --defaults ignores what was saved.
impedance has no thresholds here: its settings are changed in the Impedance panel or with
impedance, and --list-checks shows them read-only.
| Option | Meaning |
|---|---|
--cell <name> |
With a workspace: the cell whose layout to review. |
--checks id,id |
Run only these checks. |
--skip id,id |
Do not run these checks. |
--set <check>.<threshold>=<value> |
A threshold for this run, e.g. edge-copper.pullback=0.3mm. A length carries its unit (0.3mm, 300um, 12mil), and a bare number is refused, as for render --window. An area carries its unit squared (thermal-vias.thermal-pad-area=4mm2, 4e6um2, 6200mil2), and a bare number is refused for the same reason. A height is a number of substrate heights (coupled-lines.spacing=3). Repeatable. |
--defaults |
Ignore the review saved in the layout. |
--ignore-accepted |
Report as if nothing had been accepted: every finding counts. |
--as pcb\|mmic |
Review as a board or as a die: which checks run. Naming a check the other kind runs, in --checks, --skip or --set, is refused. |
--all |
List every finding. By default at most 20 per check per layer are listed, followed by … and N more. |
--severity warning\|none |
What decides the exit code. Default none: findings are advice. warning makes an unaccepted finding exit 1. |
--list-checks |
List the checks of this kind of review with their thresholds, their defaults and the layout's saved values, and run nothing. With no path it lists the defaults. |
-o report.pdf |
Also write the review as a PDF — the report the Review panel's Export PDF writes. The path must end in .pdf. |
The PDF is short whatever the board: a summary page, then for each check with findings one map page and
one page of findings. The summary page names the layout, its technology and
the date, and has one row per check: the thresholds it ran with (a threshold changed from its default is
starred and says the default), its findings and accepted counts, and what it came to — No findings, the
layers its findings are on, or Not run with the sentence that fixes it. The map page has a map of each layer the check found
something on — the whole page for one layer, side by side for several — each drawing the board outline
and that layer's copper, faded, in the technology's colour, with each finding outlined and numbered; findings too
close to draw apart are one badge carrying their count. A stacked-traces map is of the upper layer, with the
lower layer's copper outlined. A findings page lists the first 20 findings with where each is and its sentence, then
… and N more, and after them the accepted findings with their reasons; --all or --json has every one. A
cancelled run's PDF holds the checks that finished and says on its first page that the run was cancelled.
stdout gives, per check, a line with its count — accepted findings counted separately — and the
findings grouped by layer under it, each with where it is in the layout's own unit. Lengths and areas are said in
that unit too (an area in it squared), in the sentences and in --list-checks. A check that cannot
run on this technology says not run and the one change that would let it, and the run carries on with
the others: a technology with no board-outline layer, for one, cannot say where the board edge is. stderr
says which check is running.
Accepted findings apply by default. An accepted finding is reported ACCEPTED with its reason and
does not count. A saved acceptance that matched nothing is listed as stale. There is no --accept: a
script that must accept a finding headlessly adds it to the .clay's ReviewAcceptances with the
finding's key; reference layout documents the fields. An impedance finding is the
exception: it is accepted in the Impedance review (the .clay's ImpedanceAcceptances), and is reported
ACCEPTED when every finding on its trace is; a ReviewAcceptances entry never covers it. Its key is the
Impedance review's trace key, layer|x,y|x,y with the trace's end points in whole µm.
With --json the result is review: target (pcb or mmic), technology, displayUnit, warnings (unaccepted), accepted,
notes (checks not run), checks (each id, title, state — ran, not-run or cancelled
— reason, findings, accepted, its thresholds with value, default and text, and a
summary where the check states one), and
findings (each check, layer, severity — warning, or note for a check not run —
sentence, measured, key, marker as a bounding box in µm, and accepted with its reason
and date), then staleAcceptances. With --list-checks the result is reviewChecks instead, where
impedance carries its Impedance review settings as settings.
Exit codes: 0 when it ran, whatever it found; 1 on a refusal, or with --severity warning when
an unaccepted finding remains; 130 on a cancellation, reporting the checks that finished. review
writes nothing but -o's PDF.
chain — an RF chain budget, headless
$ circuitrf chain <rx.cchain> [-o budget.{npy,mat,txt}] [--to-schematic [dir]] [--name <stem>] [--replace] [--demo]
chain budgets a .cchain — the document the RF Chain Budget window edits — and prints
the cascade, one row per stage, and the chain's totals: gain, NF, the intercepts and compression points, the noise
floor, MDS, sensitivity, SNR and SFDR. The budget is a DataSet, so --json carries it and -o writes it: per-stage
cubes over a stage axis labelled with the stage names (G_dB, NF_dB, IIP3_dBm, …) and the totals as scalars.
--to-schematic is the window's Transfer to Schematic: the chain's 1-Tone, 2-Tone, Envelope and Levels
test benches; Blockers for a chain with blockers, Canceller for one with a canceller; an array's Steering and
Combiners benches; and with --demo (or "Demo": true in the .cchain) the Demo bench, the chain demodulated end
to end. Each comes with its Data Display, beside the .cchain or in dir. A bench that already exists is a refusal naming it; --replace writes it again. Nothing is written when any
bench is refused. Exit 0 when it budgeted (and
transferred), 1 on a refusal.
smith — a matching network, headless
$ circuitrf smith <match.csmith> [--at <freq>] [-o out.{s1p,svg,pdf,png}]
smith evaluates a .csmith — the document the Smith Chart window edits. It
walks the cascade from the generator to the load at the design frequency, prints what the window's
status strip states, and then prints the walk one node at a time.
That table is the reason to run this rather than open the window. The reading answers is it matched; the table answers where did it stop being matched, which is the question you have when the answer is no — and it is what you diff between two revisions of a network.
$ circuitrf smith Lmatch.csmith
L-match demo — Smith chart, Z0 50 Ω
design frequency 2 GHz
generator 10 − j10
load 17.81 + j20.38
Γ 0.538 ∠131°
VSWR 3.329
mismatch 1.48 dB
node element Z (Ω) Γ
0 (generator) 10 − j10 0.6778 ∠-157°
1 L1 10 + j17.65 0.6991 ∠140°
2 C1 17.81 + j20.38 0.538 ∠131°
Where it evaluates
--at moves the design frequency for this run only — the document is not touched. Write the unit and
it is honoured (2.4 GHz, 900 MHz, 1.9e9); a bare number is hertz.
Outside the generator table's span it refuses, with the span in the sentence. The generator impedance is interpolated between the table's rows and never extrapolated past them, so a frequency the table cannot answer for has no answer at all. The one exception is a single-row table: one row is one impedance, flat, and every frequency is legal against it.
There is no --sweep. The swept band is always walked, across the generator table's own span,
so there is nothing to switch on — and a single-row table has no band, because one row is one
impedance and a band needs two ends.
What it writes
-o |
You get |
|---|---|
out.s1p |
The load reflection coefficient as Touchstone: the whole band, or one point at the design frequency when the generator table states a single frequency and there is no band. Referenced to the chart's own Z₀. |
out.svg, out.pdf, out.png |
The chart — the trajectories, the constant-Q arcs, the swept band, the load points and their frequency labels, the conjugate-match targets, your overlays and your markers. The same picture the window's Copy chart puts on the clipboard. |
.s2p and the rest are refused: what this verb has is the load, which is one reflection
coefficient, and a two-port built from it would be three quarters invented.
The picture takes the same options plot takes — --size, --scale/--dpi,
--background, --dark.
What it will not guess
--set is refused. A .csmith states every element value as a number, with no expressions and no
variables, so there is nothing for an override to replace — and a flag accepted and then dropped means
the run answered a different question than the one you asked. --at moves the design frequency;
anything else is a change to the document, which you make by writing it.
An overlay that does not resolve is a warning and not a refusal: reference material that is missing must not take the work down with it, so the chart still draws everything that did resolve and the warning names what did not.
snp — a Touchstone file, at a glance
$ circuitrf snp <file.sNp> [--params S11,S21|all|reflections|default]
[-o out.{svg,pdf,png,cdd}]
snp is the S-Parameter Viewer with no window. It prints what the file
states — ports, the option line, the frequency range and how it is spaced, whether a sample is at
0 Hz, the reference impedance, whether it carries noise data — and names the terms it would plot.
$ circuitrf snp amp.s2p
amp.s2p
Ports 2
Parameters S, MA (magnitude, angle)
Unit GHz
Points 41
Start 1 GHz
Stop 5 GHz
DC point No ⚠
Spacing uniform, step 100 MHz
Reference 50 Ω
Noise data none
Plotted S11, S12, S21, S22
Those lines are the viewer's own, so a terminal and the window cannot spell one file differently.
Which terms
--params chooses them, comma-separated: S21, or S(2,1) once a file has ten ports or more, where
S101 would be ambiguous. all, reflections and default are words. The default is every term up
to four ports, and above that the reflections plus each forward neighbour — the viewer's own opening
selection. A term the file does not have is a refusal naming the file's port count.
What it writes
-o |
You get |
|---|---|
out.svg, out.pdf, out.png |
The viewer's four plots: reflections on a Smith chart, transmissions on a polar chart, magnitude in dB and phase in degrees, over exactly the file's own frequency range. |
out.cdd |
The same plots as a Data Display — what the viewer's Save as Data Display writes for this file, so the two cannot drift. |
The picture takes the same options plot takes — --size, --scale/--dpi,
--background, --dark.
What it reports, and the exit code
A file whose name claims a different port count, whose frequencies are not strictly increasing, or whose reference impedance has no positive resistance, is reported in the viewer's own sentence and the command exits 1. Anything you asked to be written is written first: a picture of a broken file is still the picture the window shows of it.
Passivity, reciprocity and causality are not this verb's business. check measures all three.
convert — layout interchange
$ circuitrf convert <input> -o <output> [options]
Reads a layout in any format circuitRF understands and writes it in any other. It is the same reader and the same writer File ▸ Import and File ▸ Export run — see Interchange for what each format can and cannot carry — so a conversion here and the same conversion through the GUI produce the same bytes.
| Format | Named by | As input | As output |
|---|---|---|---|
| circuitRF layout | .clay |
the file | a folder of cells plus a .ctech |
| GDSII | .gds, .gdsii, .gds2 |
✓ | ✓ |
| OASIS | .oas, .oasis |
✓ | ✓ |
| DXF | .dxf |
✓ | ✓ |
| Gerber + Excellon | a folder, or one Gerber/drill file | ✓ | a folder |
| Board | .kicad_pcb |
✓ | ✓ |
| IPC-2581 | its root element, or .cvg |
one step | refused: import only |
| STEP | .step, .stp |
into a new .c3d |
from a .c3d, a .clay, a cell folder, or any source above |
Every ordered pair works — DXF to Gerber, Gerber to board, GDSII to DXF, board to GDSII, and the
rest. There is no privileged direction and no hub format you have to route through by hand: a
conversion is an import followed by an export, and convert does both.
Formats are read off the paths. A folder means Gerber; a file with no telling extension is classified
by its content — an OASIS file by the signature it begins with, anything else through the same
classifier the Gerber import uses. --from and --to override
that, and --to is required when the output is a folder, since a folder could be either Gerber or
.clay.
Examples
Board file out to a fab house as artwork plus drill:
$ circuitrf convert board.kicad_pcb -o fab/ --to gerber
A folder of Gerbers back to a board file:
$ circuitrf convert fab/ -o recovered.kicad_pcb
A board drawn as DXF outlines, read as copper and vias — every layer model space reaches needs an answer:
$ circuitrf convert board.dxf -o cells --to clay --tech pcb-2layer_RO4350B_20mil_1oz --dxf-units 4 --layer-map "PATTERN=Top Copper,HOLE=Drill,PARTS=ignore,SHELL=Outline"
A mechanical drawing straight to artwork — no board tool in the middle:
$ circuitrf convert outline.dxf -o gerbers/ --to gerber
A mask set to a drawing your mechanical engineer can open, at the DXF version their tool wants:
$ circuitrf convert mmic.gds -o mmic.dxf --dxf-version AC1015
Bring a board in as editable circuitRF cells and keep the technology it declared:
$ circuitrf convert board.kicad_pcb -o cells/ --to clay
An IPC-2581 file, after asking what it holds:
$ circuitrf convert export.xml --list-steps
$ circuitrf convert export.xml -o cells/ --to clay --step board
Bring a Gerber set in on a stackup you already have, writing no technology of its own:
$ circuitrf convert fab/ -o cells/ --to clay --into-tech tech/pcb-6layer_FR-4_1.6mm_1oz.ctech
clay target is a folder, not a file
An import writes one cell folder per structure the source holds, plus the technology beside
them — so -o cells/ is the shape, and -o cells/board.clay is
refused with the folder spelling in the message. Point it at a folder and look inside: the
.clay is at cells/<Cell>/layout/<Cell>.clay, which is where every
other circuitRF tool expects a layout view to be.
One cell out of a GDSII library that holds many:
$ circuitrf convert lib.gds --list-cells
$ circuitrf convert lib.gds -o coupler.dxf --cell COUPLER
Convert a directory of drawings in one line:
$ for f in dxf/*.dxf; do circuitrf convert "$f" -o "gds/$(basename "${f%.dxf}").gds"; done
OASIS, and the second GDSII route
OASIS is read and written by gdstk, the same reader and writer File ▸ Import ▸ OASIS (gdstk)… and File ▸ Export ▸ OASIS (gdstk) use; Interchange says what an OASIS file keeps and what it does not.
$ circuitrf convert mmic.gds -o mmic.oas
$ circuitrf convert mask.oas -o cells/ --to clay --tech process.ctech
$ circuitrf convert Amp/layout/Amp.clay -o amp.oas --oas-compression 9
An OASIS export takes the Export OASIS dialog's options as flags, at the dialog's defaults:
--oas-compression 0–9 (default 6), --oas-validation none|crc32|checksum32 (default crc32) and
--oas-standard-properties. They apply to an OASIS target only; on any other conversion they are
refused rather than ignored. Two runs with the same options write the same bytes.
--engine gdstk sends a GDSII end through gdstk too, instead of circuitRF's own GDSII reader or
writer, which stay the default (--engine native). It is the command-line twin of GDSII (gdstk)… in
the File menus. A conversion with no GDSII end refuses the flag. When a GDSII file reads differently from
what you expect, converting it once each way and comparing is a quick way to see whether the file or the
reader is at fault:
$ circuitrf convert lib.gds -o native/ --to clay
$ circuitrf convert lib.gds -o gdstk/ --to clay --engine gdstk
gdstk runs as a separate program shipped beside circuitRF. On an installation without it, a conversion
with an OASIS end, or with --engine gdstk, is refused before anything is read, and says why.
STEP, both ways
A STEP file is a solid model, so it goes into a 3D view, and anything circuitRF can draw in 3D comes out as one. Both directions call the function the 3D editor's own dialog calls — Import STEP… and Export STEP… — so a conversion here and the same one in the editor produce the same document, and the same bytes but for the file's time stamp.
$ circuitrf convert body.step -o Connector/3d/Connector.c3d --material "flange=Connector alloy"
$ circuitrf convert Launch/3d/Launch.c3d -o launch.step
$ circuitrf convert board.kicad_pcb -o board.step
- Import writes a new
.c3d, with oneStepobject per solid part and the STEP file copied beside it. The target must be a.c3dpath. Each part takes a material as the dialog's table would give it — by its name, then its colour, from the technology the new file resolves (--technames another) — and--material <part>=<material>answers any the file cannot.--part <path>imports only the named parts (their occurrence paths,1/2). A part nothing matches is imported with no material, and the run says so: it is drawn, and it is left out of a solve until it is given one. - Export writes one file, flattened with the overlap precedence applied, in millimetres (inches for a
mil or inch design), AP214.
--assemblykeeps placed cells as sub-assemblies,--as-drawnwrites each object as drawn rather than cut by precedence,--thicken-sheetsgives sheets their thickness,--include-airboxadds the first 3D setup's air box,--schema ap242and--view 3d|layout(for a cell with both) do what they say. A source that is not already circuitRF's — GDSII, a board — is imported first, exactly as the other conversions do.
The geometry kernel does the work in both directions, so on an installation without it both are refused, saying why and how to restore it.
glTF, for another renderer
A 3D view comes out as binary glTF (.glb): the model as the 3D view draws it, with its
appearances as glTF materials and its smooth normals, for a path tracer or any other
renderer. It calls the function File ▸ Export ▸ glTF… calls, so a file written here and one written from the editor
are the same bytes when neither includes a camera.
$ circuitrf convert Package/3d/Package.c3d -o package.glb
$ circuitrf convert Package/3d/Package.c3d -o package.glb --gltf-assembly
$ circuitrf convert cell/3d/cell.c3d -o hot.glb --gltf-field Top
- The source is a
.c3d. What it shows is the document's own: an object the file hides is left out, and so are the air box, ports, boundaries and reference images. --gltf-assemblywrites each placed cell as a node that shares its meshes; without it everything is flattened.--gltf-field <plot>adds a Surfaces or Faces field plot as an unlit mesh with per-vertex colours (at phase 0).--region <name>names the region a Surfaces plot of a volume quantity is drawn on, asrender --regiondoes. A ClipPlane plot is refused.- The file carries the Look's camera when the document saves one (Set Camera in the Look panel), and no camera otherwise.
- circuitRF writes glTF and does not read it: a
.glbor.gltfsource is refused, and so is a.gltftarget.
Options
| Option | What it does |
|---|---|
-o, --output <path> |
The file to write — or the folder, for gerber and clay; a file-shaped path there is refused. Required. |
--from <fmt>, --to <fmt> |
clay, gdsii, oasis, dxf, gerber, board, step, ipc2581 (a source only), gltf (a target only). Say it when the path does not. |
--engine native\|gdstk |
Which GDSII reader or writer a GDSII source or target goes through. Default native. See OASIS. |
--oas-compression <0-9>, --oas-validation none\|crc32\|checksum32, --oas-standard-properties |
OASIS export: the Export OASIS dialog's options. Defaults 6, crc32, off. |
--cell <name> |
Which cell to export, when the source holds several. |
--list-cells |
Report what the input holds and write nothing. |
--step <name> |
Which step of an IPC-2581 file to import. A file holding several is refused until one is named, and the refusal lists them. |
--list-steps |
Every step of an IPC-2581 file with its layer and net counts, writing nothing. |
--name <stem> |
What to call the written Gerber file set. Default: the cell's name. |
--tech <t> |
The technology to convert against, instead of the one the layout resolves: a .ctech file, or the id of a technology that ships with circuitRF. |
--into-tech <file.ctech> |
A Gerber set, a board file or an IPC-2581 file into a clay folder using this technology: the cells reference it, nothing is added to it, and no .ctech is written. Refused together with --tech, and for any other source. See below. |
--workspace <file.cws> |
The workspace a .clay's references resolve against. Default: the nearest one above it. |
--keep-cells <dir> |
Keep the cells the import produced instead of discarding them. |
--dbu <n> |
Database units per micron for an imported design. Default 1000 — one DBU is one nanometre. |
--dxf-version <v> |
AC1015 (R2000), AC1018 (R2004), AC1032 (R2018, the default). |
--dxf-units <n> |
The $INSUNITS value for a DXF that declares none. |
--layer-map <map> |
A DXF's layers, as LAYER=target,…, the Layer Mapping dialog's answers. target is ignore, add or a layer of --tech, which reads as its role: a conductor fills, a drill layer makes vias, the outline closes. target:invert takes a conductor's other parity and target:drawn keeps the entities as they are. Needs --tech. The result is one cell named after the file, with INSERTs flattened. Without the flag a DXF imports a cell per block, every layer as drawn. |
--join-tolerance <len> |
With --layer-map, how far apart two open ends may close, with its unit. Default 1um. |
--drill-units <mm or inch> |
Excellon coordinate units, when the file does not say. Applies to every drill file in the set. |
--drill-format <int>:<dec> |
Excellon digit counts, e.g. 2:4. Applies to every drill file in the set. |
--drill-zeros <leading or trailing> |
Excellon zero suppression. Applies to every drill file in the set. |
--accept-inferred-drill-format |
Take each drill file's own inference rather than refusing. |
--material <part>=<material> |
STEP import: the material a part takes. Repeat it per part. |
--part <path> |
STEP import: import only this part (its occurrence path). Repeat it per part. |
--assembly, --as-drawn, --thicken-sheets, --include-airbox, --schema ap214\|ap242, --view 3d\|layout |
STEP export: the Export STEP dialog's choices, one flag each. |
--gltf-assembly, --gltf-field <plot>, --region <name> |
glTF export: the Export glTF dialog's choices. See glTF. |
--arc-tolerance <len> |
A Gerber source: how far a chord may stray from an arc where a curve has to become a polyline, with its unit, from 1nm to 100um. Default 1um, the same as Settings ▸ General ▸ Gerber Import. A bare number is refused. |
--open-archives |
Look inside an archive when a Gerber folder holds no artwork of its own. It is unpacked to a temporary folder, imported from there, and deleted again; nothing is added to the folder you named. Without this flag, such a folder is a refusal that names the flag. |
Which cell gets exported
A GDSII library, a DXF drawing and a board file can all hold more than one cell, and an export writes
one design. Unless --cell says otherwise, convert takes the source's own idea of the top: the
GDSII structure nothing else instances, DXF's model space (the drawing itself, not a BLOCK
definition), the board rather than one of its footprints. A Gerber set is always one flat cell. When
the source genuinely has no unambiguous top, the conversion stops and tells you to name one —
--list-cells prints the choices.
The technology, and why it matters here
An import brings a layer table with it, and in the GUI those layers land on the technology your
workspace already has open. Headless there is no open workspace, so convert writes a .ctech of
its own from what the file declared, exactly as File ▸ Import ▸ Gerber does. That is what keeps
layer names, colours and Gerber file suffixes alive across a conversion instead of leaving every layer
a bare number.
Two consequences worth knowing:
--techis how you convert against a process you already have. Point it at a.ctechand the source's layers reconcile against it — matched layers keep your names and your Gerber suffixes, unmatched ones are added. Without it, an intermediate technology is invented from the file alone, and a Gerber export then names its files from synthetic suffixes.--into-techimports a Gerber set or a board file into a technology you already have, the way the Technology row of the File ▸ Import ▸ Gerber and Board layer-mapping dialogs does. Each layer lands on that technology's own layer, the cells reference the file you named, and no.ctechis written. A Gerber set is refused, and nothing is created, when its copper does not fit that technology's stackup: a different number of copper files than conductors, a copper file on a conductor of a different rank, two on one conductor, or drill data on a layer no via binds. A board is refused when its number of copper layers is not the stackup's number of conductors. It takes aclaytarget only. The other importers already keep the stackup of the technology--technames, so the flag is refused for them.--keep-cells <dir>leaves a design you can open. Cells plus the technology they point at — the honest way to see what a conversion actually understood before you send the result anywhere.
GDSII is the one exception, and it is the format's own doing. GDSII identifies a layer by a
number, not a name, so an import has nothing to name it with: the numbers come through exactly, the
names do not. Convert from GDSII with --tech pointing at the technology those numbers belong to and
the names come back. An OASIS file may name its layers; when it does, a name is matched against
--tech before the number is, and when it does not, OASIS behaves as GDSII does.
When it refuses
A DXF layer --layer-map does not name is a refusal, and so is a name model space does not reach. The
refusal lists the layers it does reach, and nothing is created.
Many Excellon files do not say whether their coordinates are inches or millimetres, or whether
leading or trailing zeros are suppressed — and leading versus trailing differ by four orders of
magnitude on identical text. The GUI asks you. There is nobody to ask here, so the conversion
stops, prints what it inferred and the evidence behind it — including whether the holes land inside
the artwork's own outline — and names the flags that answer it. Accept the inference with
--accept-inferred-drill-format, or state it outright with --drill-units,
--drill-format and --drill-zeros.
A --drill-* flag settles the whole set, not the first file. A drill flag is a statement about
the run — one exporter wrote the .drl and the .rou next to it in one format — so it applies to
every drill file the conversion reads, and the refusal is printed once rather than once per file.
--accept-inferred-drill-format works the same way, with one difference worth knowing: it accepts
each file's own inference rather than forcing the first file's format onto the rest.
$ circuitrf convert fab/ -o board.kicad_pcb --drill-units mm --drill-format 3:4 --drill-zeros leading
Reach for the flags less often than you might expect: a file that writes every coordinate at its full width — same number of digits throughout, leading zeros intact — states its own format by doing so, and the conversion reads it off the coordinates and says as much. The flags are for the files that leave a genuine question, and the note printed for every drill file names which parts of its format were declared, which were inferred, and from what.
It also stops, rather than guessing, when a design instantiates cells drawn against a different
technology and the layer mapping needs confirming; when a coordinate overflows GDSII's 32-bit range, or a layer, datatype or array count overflows its 16-bit one (an OASIS export is held to the same limits); when an OASIS
repetition would expand into more than a million shapes; and
when the source holds several cells and none of them is an unambiguous top. Every refusal exits 1
and writes nothing at all.
Everything short of a refusal is a note on stderr, counted and named: labels flattened to geometry, curves turned into polygons, holes keyholed, bitmaps dropped, unresolved instance references, layers with no mapping in the target format. stdout carries only the paths written, one per line, so a script can consume them directly:
$ circuitrf convert board.kicad_pcb -o fab/ --to gerber 2> convert.log | zip -j fab.zip -@
new — a workspace or a cell
$ circuitrf new workspace <dir> [--name N] [--tech <id>|none] [--length-unit um|mm|mil]
[--vendor-library <name>=<folder>]...
$ circuitrf new cell <workspace> <cellName> [--views schematic,symbol,layout]
$ circuitrf new example <dir> <name>
These create the first correct document — the thing that is awkward to write by hand because the folder structure and the primacy files have to be right before anything will open it.
They are not a second implementation. new workspace calls the same function File ▸ New
Workspace calls, and new cell the same one New Cell calls, so a tree created here is a tree
the application created.
$ circuitrf new workspace ~/designs/Amp --tech pcb-4layer_FR-4_62mil_1oz
/home/you/designs/Amp
/home/you/designs/Amp/.cws
/home/you/designs/Amp/tech/pcb-4layer_FR-4_62mil_1oz.ctech
$ circuitrf new cell ~/designs/Amp Stage1 --views schematic,symbol
/home/you/designs/Amp/Stage1
/home/you/designs/Amp/Stage1/schematic/Stage1.csch
/home/you/designs/Amp/Stage1/symbol/Stage1.csym
--length-unit writes the copied technology's display unit — what a preferred length unit in Settings ▸ General
▸ Units does for File ▸ New Workspace. Left out, the technology keeps its own.
--vendor-library references a vendor part library from the start, and
may be given more than once. File ▸ New Workspace references the libraries you have ticked for new
workspaces; headless there is no such list, so nothing is referenced unless you name it here.
The paths it creates are the result, on stdout, because what you do next is almost always read or rewrite one of them.
Whatever the GUI's dialog pre-selects, the verb selects with no flag: --tech opens on
the same technology the New Workspace combo box opens on (--tech none is its
"None" row), and --views defaults to schematic, which is what New
Cell creates. Anything the dialog would have asked is a refusal that names the flag
answering it — never a guess.
There are deliberately no per-primitive edit verbs. There is no place-instance and no
set-parameter: once a document exists, the way to change it is to write it. Every format
circuitRF owns is readable, versioned JSON — see File formats — and that file
is the interface.
new example — a copy of a shipped example
new example is File ▸ New ▸ Examples without the window: it copies one of the
example workspaces to <dir>/<name>, where you can run it and change it. The
shipped copy is never written to. A folder that already exists is refused, never overwritten, with
the same sentence the menu gives.
$ circuitrf new example ~/designs Loadpull
/home/you/designs/Loadpull/.cws
/home/you/designs/Loadpull/README.md
<name> is the example's folder name or its menu title, ignoring case.
new is one verb with a noun
new workspace and new cell are two nouns of one verb, not two verbs. It reads better and, more to
the point, the number of top-level verbs is a cost every reader of --help pays.
import part — a footprint and its symbol
$ circuitrf import part <file-or-folder> --into <workspace> [--cell N] [--variant V]
[--list-parts] [--tech f.ctech] [--add-layers]
The same code Import Component runs: it reads a downloaded component — a land pattern, a symbol, and the pin-to-pad map that joins them — and writes it into your workspace as one cell.
$ circuitrf import part downloads/SOT-23.zip --into ~/designs/Amp --list-parts
SOT-23-3
SOT-23-5
$ circuitrf import part downloads/SOT-23.zip --into ~/designs/Amp --cell SOT-23-3
/home/you/designs/Amp/SOT-23-3
/home/you/designs/Amp/SOT-23-3/layout/SOT-23-3.clay
/home/you/designs/Amp/SOT-23-3/symbol/SOT-23-3.csym
A source holding several parts is refused with them listed, never resolved by taking the first.
Layers the technology does not have are reported, and nothing is written, unless you pass
--add-layers. The GUI's own install is session-only and writes nothing to disk either, so this is
the same behaviour and not a headless restriction.
import library — a vendor part library, referenced in place
$ circuitrf import library <folder> --into <workspace> [--name N]
The same code File ▸ Add Vendor Library… runs: the folder is indexed, and the workspace's .cws
gains a reference to it under --name (the folder's own name when left out). Nothing is copied.
Adding a name that is already referenced repairs it to the new folder.
$ circuitrf import library ~/libraries/caps --into ~/designs/Amp --name Caps
Caps /home/you/libraries/caps
12,410 parts in 31 files.
Folders: general (8,902), automotive (3,508).
2 files are encrypted for another simulator and cannot be read. The plain-text (static) edition of this library can be.
A folder with no readable part is refused (exit 1) with that sentence. --json carries the counts and
the folder groups as result.library.
A run verb given a .csch that places vendor parts says two things on stderr, each a warning that leaves
the exit code alone: a placed part that could not be read (run.vendor-part.unreadable, naming the part
and the library; the run goes without it), and a part the run's analyses take outside its stated
frequency range (run.vendor-part.out-of-range, once per part number). A DC operating point never
warns about range.
export part — a cell as a model in a model library
$ circuitrf export part <cell folder|file.cmc|file.cmodel> --library <dir> [--category a/b] [--part PN] [--description …]
[--kind active|passive] [--value …] [--freq-low … --freq-high …] [--conditions …] [--footprint …]
[--doc <file>]… [--symbol own|fet|r|c|l|box] [--replace] [--include-vendor-parts]
[--compile-models] [--build <rid>=<path>]…
[--protect encapsulate|compile] [--compile-netlist] [--source-out <file.va>] [--check-bias "pin=V …"]…
[--library-name … --publisher … --license … --library-version …]
The same code File ▸ Export to Library… runs: the cell, its sub-cells and every file they use are copied
into a new model folder, its netlist and part.cpart are written, and the
folder is checked and self-checked against the source cell before it appears under --library. An empty or
new folder starts a new library, with --library-name, --publisher, --license and --library-version
(1.0.0) written to its library.clib.
$ circuitrf export part ~/designs/Amp/TSTLNA100 --library ~/libraries/devices --category Amplifiers
/home/you/libraries/devices/Amplifiers/TSTLNA100
/home/you/libraries/devices/library.clib
/home/you/libraries/devices/Amplifiers/TSTLNA100/.ccell
…
A Model Composer document (.cmc) or a model (.cmodel) in place of the cell folder publishes the model as Model
Composer's Export to Library does: the cell is written for it, beside the document, and deleted afterwards
(Publishing to a library). A .cmc publishes the .cmodel its Export step writes
beside it, and is refused, with the button's sentence, when that has not been exported or the document changed since.
The part number and frequency range default to the .cmodel's name and the measured data's range.
Every default is the dialog's. A refusal exits 1 and writes nothing: a kit part, a file that does not resolve,
a part that takes its substrate from the technology, a cell with no Pin, or a self-check that does not match.
A part number already in the library needs --replace (its Revision goes up by one), and a cell that places
vendor parts needs --include-vendor-parts, because their libraries' terms travel with the model. A
cancelled export exits 130. --json lists every path created.
A compiled .osdi the cell places ships as this platform's build in models/<rid>/
(Compiled-only models). --compile-models compiles each Verilog-A source the
cell places, and each ComposedFET converted to Verilog-A, and ships the build instead of the source; it is off unless given, as the dialog's checkbox is. Each
--build <rid>=<path> adds a build made on another platform, such as --build win-x64=builds/TSTFET100.osdi. The
self-check runs this platform's build only, and the last line says so.
--compile-netlist publishes the whole cell as one compiled Verilog-A module instead of its schematic
(Compile netlist); it is off unless given. The generated source goes to
protected-source/<Part>/<Part>.va in the workspace folder, or to --source-out, never into the library. Each
--check-bias is one bias point of the proof (pins not named are at 0 V); without any, the proof runs every pin at
0 V and each pair of pins at ±1 V. Every platform the compiler can reach is tried. Standard error lists them, led
by "This part will be unavailable on: …" when any is missing, and --json carries the same rows as exportPart's
builds[] (rid, state, reason, fix) beside the source path, compiledFrom and the proof's verdict. A
missing platform does not change the exit code. Run the same command on another platform into the same library to
add that platform's build: it is added when the source hashes the same, and refused when the cell has changed.
--protect encapsulate publishes the cell's linear network as one S-parameter block, with its devices placed
against its ports (Encapsulated netlist); it needs no compiler and is off unless
given. The block is sampled over the --sweep plan (0-20 GHz, 1001 points, when none is given), and the proof runs at
the --check-bias points as Compile netlist's does. --protect compile is --compile-netlist; the two ways cannot be
combined. --json's exportPart carries the proof's verdict.
$ circuitrf export part <model folder> --refresh
After a model's schematic was edited by writing it, --refresh does what saving it in Edit Vendor Model…
does (A placed part): a file it newly names is copied into models/, a newly
added part that takes its substrate from the technology is converted, and its netlist, pins and parameters are
written again. Its published hash and revision are left as they are. It prints each file written. When the
schematic cannot become the netlist it exits 1 with the reason and writes nothing, and the model runs its
previous netlist.
import model — a library model, as a workspace cell
$ circuitrf import model <model folder> --into <workspace-or-dir>
The same code Copy to Workspace… runs: the model folder is copied in as an ordinary cell named after its part
number (TSTFET100_2 when the name is taken), its file references rewritten to the workspace's form, without its
part.cpart and netlist, and with a CopiedFrom note in its .ccell that explain prints. It prints the cell
folder. Placed parts are not pointed at the copy: change the part's CellRef in the .csch to do that.
import part handed a model folder refuses and names this command, because a model is already a cell rather
than a symbol and footprint to read.
sign — sign a model library
$ circuitrf sign --new-key <private.pem>
$ circuitrf sign <library> --key <private.pem>
--new-key writes a new P-256 private key and prints its path and fingerprint; it never overwrites a file. With a
library, sign recomputes every model folder's hash, writes library.sums and signs it into library.sig, as
Signed libraries describes. It exits 1 and writes nothing for a key path inside
the library, a library whose library.clib names no Publisher, or a library that fails check with an error.
--json lists the two files and the fingerprint. check on a signed library reports what the signature means for
you.
import schematic — another editor's schematic, as a cell
$ circuitrf import schematic <file> --into <workspace-or-dir> [--cell N] [--port <ref-or-net>]...
[--symbol-cell <ref>]... [--codepage N] [--list]
The same code File ▸ Import ▸ Schematic… runs, with the dialog's defaults: the cell is named after the file, every part goes where the import's map sends it, and no Port is made. The format is recognised from the file's content.
| Option | |
|---|---|
--port <ref-or-net> |
make a connector (by reference) or a net (by name) a circuitRF Port; repeat it, in port order. With any Port, SP1 is added |
--symbol-cell <ref> |
keep a part that would become a native R, L or C as the source's own symbol |
--codepage N |
how EDIF and binary .DSN text that is not UTF-8 is decoded; 1252 by default |
--list |
print the pages, one part line per part with what it becomes, and one port line per Port candidate; write nothing |
$ circuitrf import schematic matching.kicad_sch --into ~/designs/Amp --port J1 --port J2
/home/you/designs/Amp/matching
/home/you/designs/Amp/matching/schematic/matching.csch
stdout is the paths written: the cell, its schematic and every symbol or sheet cell. The import's report goes to stderr. Exit 1 when the connectivity check is not matched, with the cell written so the differences can be looked at; any refusal writes nothing and exits 1. What becomes what, and what the check compares, is in Importing a schematic.
render — a picture of a document
$ circuitrf render <path> -o <out.svg|.pdf|.png> [options]
Turns a schematic, a symbol, a layout or a data display into a file you can look at, put in a report, or diff between two commits. Every pixel comes out of the renderers the editors draw each frame with — the same Skia that produces the picture on the canvas — so the file is what the window would have shown, not an approximation of it.
One verb over every document kind, inferred from the path exactly as check and explain infer
it. There is no render-schematic.
| Path | Resolved by |
|---|---|
a .csch, .csym, .clay or .cdd |
directly — including one in a folder with no workspace above it |
| a cell folder | --view, or the one view it holds. More than one is a refusal listing them |
| a workspace | --cell <name> — rendering "the workspace" is not a picture of anything |
$ circuitrf render ~/designs/Amp/Stage1/layout/Stage1.clay -o stage1.png
Wrote stage1.png (1600x1200 device-pixels, 11,540 bytes)
4 of 4 shape(s) drawn, 12 vertices emitted, 7 draw call(s)
-o is required and its extension picks the format — .svg, .pdf or .png; --format
overrides it. There is no picture on stdout: stdout is the result, and --json has to be able to
co-exist with the write.
It resolves no technology, draws on the generated fallback palette exactly as the layout editor
does with an unresolved technology, and says so as a note — not a warning and never a refusal.
A .clay a converter just handed you has no workspace by construction, and treating that
as a problem would teach you to skip the warnings that are problems.
The viewport, and the unit rule
$ circuitrf render <path> -o out.png --fit [--margin 0.10]
$ circuitrf render <path> -o out.png --window x0,y0,x1,y1
$ circuitrf render <path> -o out.png --center x,y --span w
The three are refused together rather than ordered. A precedence nobody stated is an invention, so
--fit --window … stops rather than quietly picking one.
--window 0,0,500,300 is refused. It could mean database units, micrometres or
millimetres, and those are three pictures six orders of magnitude apart — all of which come back
looking entirely reasonable. Write --window 0um,0um,500um,300um; the
suffixes are nm, um (or µm, or u),
mm, mil and in. The refusal prints the same number spelled
the two ways you most plausibly meant, plus the document's own display unit when it is neither.
A schematic or a symbol takes bare numbers, because its coordinates really are dimensionless
design units — and --json says "unit": "design-units" rather than leaving you
to assume metres.
You do not have to write one from scratch.
explain --extents prints a ready-made
--window line for the whole document and for each layer, in exactly this spelling. Copy
it; there is no conversion to get wrong.
A window of a different shape from the page is letterboxed, never cropped and never stretched. You
get the whole region you asked for, with bars; the resolved window is in the --json document with
letterboxed beside it. Silently giving you less of a region than you asked for is not something you
could notice from the picture.
--fit frames the box that gets painted, not the box that is stored. A label's stored extent is
its anchor point, an EM port paints a width bar and an arrow past its own geometry, and an instance's
extent resolves through the cell it places. On a symbol the two genuinely differ: pin names are
drawn in pixels, at a size with a floor, so they have no world extent until a page size is chosen — a
fitted symbol page is therefore wider than the box explain --extents reports,
which is the box you want when you are sizing a --window yourself.
Size, and what --detail costs
| Option | What it does |
|---|---|
--size WxH |
Device pixels for .png, points for .svg/.pdf. Default 1600x1200. |
--scale n |
Raster multiplier — 2 is "@2x". .png only; a refusal on a vector format, which has no pixels to multiply. |
--dpi n |
The same number spelled relative to 96 dpi. Refused together with --scale. |
--detail |
full, screen, or a pixel budget. How much geometry comes out. Layout only. |
--detail full is the default, and it is not free. full turns every level-of-detail tier off, so
what is stored is what is drawn; screen engages the tiers exactly as a canvas does at that zoom,
which is the picture a person actually looks at. Measured on a real six-layer board — 3,284 shapes and
764,032 vertices — framed whole at 1600x1200:
| Format | --detail full |
--detail screen |
|---|---|---|
.svg |
23.5 MB, 1.44 s | 6.2 MB, 0.36 s |
.pdf |
9.6 MB, 1.22 s | 2.6 MB, 0.51 s |
.png |
1.4 MB, 0.55 s | 1.0 MB, 0.35 s |
A vector file stores every vertex, so full is nearly four times the SVG for a picture that cannot
show the difference at that zoom. A raster barely moves, because its size is set by its pixels and not
by the geometry behind them.
So: --detail screen when you want the picture. --detail full when you want the geometry — a
plot you will zoom into, an SVG something downstream will read the paths out of, a diff between two
revisions of the artwork. A pixel budget (--detail 0.5) sits between them; --json reports the
toleranceDbu it actually resolved to, which is bucketed by octave and so is rarely the number you
asked for.
The verb prints the vertex count and the file size it produced, and --json carries both, so you find
this out from the answer rather than from a 23 MB file.
Layers and colour
$ circuitrf render Stage1.clay -o top.png --layers "Top Copper,Silk Top"
$ circuitrf render Stage1.clay -o nosilk.png --hide-layers "Silk Top,Silk Bottom"
Layout only, mutually exclusive, and the default is every layer. What you have hidden in the layout editor's Layers list is yours alone and does not change a render.
explain --layers before render --layers
A technology's layer list and the layers a document draws on are different sets, and only
the second one puts anything in the picture.
explain --layers gives you both at once: every layer the
technology defines, with how many shapes this document has on it.
A name that is neither in the technology nor drawn on by the document is a refusal that
lists the real ones. It is not a silent skip, because a misspelled layer and a genuinely empty layer
produce the same picture and you would have no way to tell which you were looking at. A layer the
document draws on that the technology does not define — ordinary after an import — is
accepted under the generated L<layer>/<datatype> name
explain --layers prints for it, and is excluded like any other when you name a different
one.
Framing on some layers and drawing all of them
A fit frames what it draws. So hiding a layer takes it out of the framing as well as out of the picture — which is usually what you want, and occasionally not. The case that bites is an imported board: the drill-map fabrication drawing sits far outside the board outline, and framed with everything else it shrinks the board to a corner of the page.
$ circuitrf render board.clay -o board.png --fit-layers "Top Copper,Bottom Copper"
--fit-layers frames on those and draws everything — so the drill map is still there, it simply
falls outside the frame. It is refused with --window and --center/--span, which state the frame
outright, and refused when the layers you named draw nothing, because framing on nothing is not a page.
With --json each layer row says whether the frame was taken from it.
Recolouring a layer for one picture
$ circuitrf render board.clay -o copper.png \
--layers "L1 Copper,L2 Copper,L3 Copper" \
--layer-colors "L1 Copper=#e04030,L2 Copper=#30a050,L3 Copper=#3060e0"
A Gerber import gives every copper layer nearly the same colour, so a copper overlay comes out
unreadable. --layer-colors says how a layer draws for this render only — nothing is written to
the technology, because changing a design to change a picture of it is not a fix. The flag can be
repeated, and one entry can carry several comma-separated pairs.
Colours are #rgb, #rrggbb or #rrggbbaa. The eight-digit form sets the layer's fill opacity,
which is the alpha the renderer actually paints a fill through — a layer's own colour alpha is not read
at all, so an override that only set it would look applied and change nothing. Layer names are the ones
explain --layers prints, including the generated L<layer>/<datatype> names an
import produces; a name that is not one of them, or a value that is not a colour, is a refusal rather
than a silent skip. --json reports each layer's color and fillOpacity as drawn, which is how
you check an override landed — a colour change is the one thing a picture alone cannot confirm.
| Option | What it does |
|---|---|
--theme |
A theme name, or the path to a .ccolor file. Default: the workspace's own recorded theme, else the shipped one. A name that resolves to nothing is a refusal listing where it looked. |
--variant |
light or dark. Default light. |
--background |
opaque or transparent. Default opaque. |
--grid |
Draw the grid. Off by default, as every export is. |
--no-rulers |
Leave the rulers out. |
--outline-text |
.svg only: write every label as glyph outlines instead of live text. Refused on .png and .pdf. |
--outline-text is for an SVG that leaves circuitRF. By default an SVG's labels are live text that
names IBM Plex Sans. A browser, vector editor or animation tool that does not have that font installed
quietly substitutes another (usually a serif), with different letter shapes and widths and no warning.
With --outline-text each letter is written as the outline of the glyph circuitRF drew, at the same
position, so the file looks identical in any viewer. Nothing else in the file changes. The cost is that
the text can no longer be edited or searched as text, and the file is larger. Text in a face circuitRF
does not ship (a platform font used for a symbol neither shipped font covers) stays as text, with a
warning naming the face. A PDF already embeds its fonts and a PNG has no text, so neither needs it.
Rulers are on by default and everything else is off. A ruler is document content — it is in the
.clay and the layout editor treats it that way — so an export that dropped it would be showing you a
different document. Selection chrome, handles, the marquee, PCell pin overlays, snap glyphs and the
EM/DRC overlays are all views of editor state rather than of the file, and none of them can appear here
at all.
A data display — the same verb, a different anatomy
$ circuitrf render Amp.cdd -o amp.pdf [--data run.npy]... [--tab name|n] [--plot n] [--all-tabs]
A .cdd is the fourth document kind, and the one that is not a drawing: it holds no data. Its
traces name a source — a file, or the sentinel meaning "whatever this display has selected" — and
every curve is re-resolved from a file on disk each time it opens.
This is the rule the whole .cdd path is arranged around. An empty plot is a valid
picture: it draws, it exports cleanly, and it looks exactly like a measurement that genuinely came back
empty. So every source the pages you asked for reference is resolved before anything is drawn,
and one that cannot be is a refusal naming --data.
--data may be repeated and binds in order; the first one satisfies the document's own
selected source. A --data that binds nothing is a refusal too — handing over last
week's run must not silently get you a picture drawn from whatever was lying beside the document.
A reference is looked for beside the .cdd, then under the nearest ancestor workspace's results/.
--tab takes a name or a 1-based number (a tab literally called 2 beats the second tab), --plot a
1-based number within it, and the default is the tab the display opens on. --all-tabs is PDF's
alone — a PDF is a multi-page format and SVG and PNG are not, and inventing out-1.svg,
out-2.svg from one -o would be this tool naming your files for you. On those it is a refusal
naming --tab.
--size replaces the page and nothing else about the composition changes. The default is the
792×612 pt landscape page with 36 pt margins, and the plots, axis label strips and any marker info
boxes you dragged are fitted to it as one group — so a box you moved lands in the file where it sits on
screen. The options that describe a drawing — --window, --center, --span, --fit, --layers,
--hide-layers, --detail, --view, --cell, --grid, --no-rulers — are each a refusal naming
themselves, because a display has no world coordinates and no layers, and a --window that silently
did nothing would give you a full picture you believed was a crop.
With --json, result.render.dataDisplay names every source and the file it actually resolved to,
and which of --data or the document bound it. That is the part the picture cannot tell you: "the plot
is empty" and "the plot read the wrong run" look identical.
A 3D view's field plot
A 3D setup's model — a .cem with a 3D solver, or a .c3d — is drawn as a section with
--section z=35um (or xz@y=…, yz@x=…; every length carries a unit) or as an outline with --iso.
A .c3d also keeps its field plots as records, so a plot can be drawn with no window. A
clip-plane plot is a section: it is drawn on its own axis at its own position, with the field under
the model's outlines and its legend beside it.
$ circuitrf render cavity/3d/cavity.c3d --list-fields
Field1: |E| · mode 1 · clip z = 12.5 mm · setup 'modes'
data: ready
$ circuitrf render cavity/3d/cavity.c3d -o cut.svg --field Field1
Wrote cut.svg (1600x1200 points, 18,513 bytes)
XY section at z = 12.5 mm: 1 object(s), 0 port(s)
Field1: |E| at Mode 1: 8.88028 GHz, Q 3.14E+11, 144 triangles, 0 … 2581 V/m
- It reads what the 3D view reads. The same run (the workspace's
results/, whereemwrites), the same saved solution by value, the same triangles and the same colour range. A plot whose data is missing — no run yet, a frequency the run did not save, a quantity it no longer offers — is a refusal with the sentence the Object Tree shows. It is never drawn at the nearest frequency, and an outline is never passed off as the field. - A stale run still draws, as in the window, with a
note:naming what has changed since — the model, or a file the run was solved from ('Board.clay' has changed since the run at …). --phase <degrees>picks the instant drawn for a quantity read instantaneously (Re{E}); it is a refusal on any other. The phase is never written to the file.- A hidden plot draws the same. Hiding only chooses which plot the window draws;
--fieldnames one. - The plot's drive is applied, and its legend states it, as in the window. There is no flag for it: set it on the plot. See what drive a field is shown at.
- A
--sectionthat is not the plot's own plane is a refusal naming both. - A SAR plot can be drawn as a maximum-intensity projection:
--mip x|y|ztakes, along each line through the volume parallel to that axis, the highest SAR on it, and draws the result on a plane through the middle of the tissue — where tissue is hottest, which no single section says. A plot whose own Maximum-intensity projection box is ticked is drawn that way without the flag. It is refused for any other quantity and together with--section, and it is never written to the file. See SAR. - A surfaces or faces plot is drawn as the 3D view shows it, from a direction you give, because the
window's camera is not saved with the plot:
--iso(the 3D view's Standard Views ▸ Isometric),--view-dir top(orbottom,front,back,left,right), or--view-dir x,y,z— the direction from the model toward you, so1,-1,1is the isometric. Surfaces hidden behind others are left out, the rest of the model is shaded around the field, and the edges are drawn over it. It is written as a.pngfor now. A temperature is mirrored across the document's symmetry planes, as in the window;--no-mirrordraws only the part that was solved. An EM field on surfaces is drawn on the region selected in the window's tree, so name it with--region(the refusal lists them). The hottest point is ringed and named in the legend. - A temperature plot draws a thermal section. The colours span the section's true minimum to its maximum at the plot's point, as the 3D view ranges a temperature. Each bond wire is painted from its own solved T(s): a wire is a one-dimensional element, so its temperature is not in the 3D field around it. Metals are outlined rather than filled, since they carry a temperature too; there are no ports, and the thermal boundaries (a face held at a temperature, a convection face) are drawn and labelled on the frame.
--labelswrites each solid's material inside it, where the words fit.--tightcrops the page to the section, with the legend inset in its corner (no margin unless--marginasks for one). Both are for a field plot.--axesadds the 3D view's axis indicator in the bottom-left corner, and--scale-barits scale bar in the bottom-right, in the 3D view's display unit. They work on any section; on--iso, or a surfaces plot seen from anything but along an axis, only--axesdoes.- PNG and vector differ in one way. A PNG blends colours across each triangle. SVG has no gradient
mesh, so in SVG and PDF each triangle takes the colour of its centre. A vector slice of more than
50,000 triangles merges neighbours that fall in the same step of the colour map, never dropping area.
The report says
drawn as …when that happened, and--no-thindraws every triangle.--no-legendleaves the legend off. --jsonaddsrender.em3d.field: the plot, its setup, the solution as the file spells it, the run directory it read, the triangle counts, the range and whether the run is stale — and for a temperature, each wire drawn with its temperature span and where it crosses the plane.--list-fields --jsonreturns the plots asfieldPlots.
$ circuitrf render Output/3d/Output.c3d -o wires.png --field "DC 14 A — along wire 4" --labels --tight --axes --scale-bar
Wrote wires.png (1600x811 device-pixels, 299,566 bytes)
XZ section at y = 75 µm: 9 object(s)
DC 14 A — along wire 4: T_C at Idc = 14, 4,692 triangles, 85 … 393 °C, 1 wire(s) from their T(s)
$ circuitrf render "Eight Fingers/3d/Eight Fingers.c3d" -o surface.png --field Surface --iso
Wrote surface.png (1600x1200 device-pixels, 217,677 bytes)
Temperature on every exposed face, isometric, from +x −y +z, orthographic — setup 'Array', the only point: 2 object(s)
Surface: T_C at the only point, 98,000 triangles on every exposed face, isometric, from +x −y +z, 85 … 105.1 °C, mirrored across 1 plane(s)
A see-through object for one picture
A .c3d's objects are drawn at the transparency each states: a section fills
each one at it, and a surfaces or faces plot shades the model around the field with it. An outline (--iso)
fills nothing, so it looks the same whatever an object states. --transparency sets one for this render
only — the file on disk is never written:
$ circuitrf render pkg/3d/pkg.c3d -o cut.svg --section xz@y=0mil --transparency lid=80,U1=50
- Each entry is
name=percent, a whole number from 0 to 95. The name is an object's, a placed cell's (which multiplies onto each of its parts' own), or a group's path (stage1/match=40), which sets every member of it at every depth. The flag repeats, and takes a comma-separated list. - An unknown name is a refusal that lists the objects, instances and groups the document has. A value out of
range, or anything but
name=whole number, is refused before the file is read. - It is a
.c3d's: on any other document,.cemincluded, it is a refusal naming the kind.
A realistic picture
--look realistic draws a .c3d as its realistic view looks, as a .png, with no window. It is
what Export Picture makes in the realistic view:
$ circuitrf render pkg/3d/pkg.c3d -o shot.png --look realistic --iso
$ circuitrf render pkg/3d/pkg.c3d -o shot.png --look realistic --look-set Exposure=1 --look-set Environment=Dark
- The Look is the file's. The environment, exposure, background, shadows, ground and every Show option are read
from the
.c3d's Look.--look-set Key=valuechanges one key for this picture only, spelled as the file spells it (FieldStyle=Lit,Background=#ffffff,Shadows=false,Exposure=nullfor the default). The value is checked as the file's is, soExposure=20is refused. The file is never written. - The camera.
--isoor--view-dir top|bottom|front|back|left|right|x,y,ztakes the picture orthographically, framed on the model. With neither, the camera Set Camera saved in the Look is used, perspective included. With no camera at all, the render is refused. - Quality and background.
--supersample 1|2|4(default 2) draws the picture at that many times its size each way and brings it down.--background transparentleaves out the background and keeps the soft ground shadow's alpha.--sizeand--scaleset the pixels. - A field plot.
--field <plot>draws a Surfaces or Faces plot on the model in the Look's field style. The picture carries the legend and the Lit Fields or Blended Fields label, as Export Picture's does. A ClipPlane plot is a section, so draw it without--look realistic. - What it refuses. A
.cem, an.svgor.pdfoutput, and--section,--tight,--labels,--axesand--scale-bar. - The picture is drawn on the processor, by the same shading the realistic view uses, and the same command always
writes the same bytes.
circuitrf explain pkg.c3d --looklists the Look asrenderreads it, the environment it will light with, and every appearance with where each value came from.
Reference images
A .c3d's reference images — image sheets, and images mapped onto faces — are drawn by
--iso exactly as the 3D view's Copy as Vector draws them, from the same code: under every line, each clipped to its
sheet or its face, at its transparency. An image sheet is usually not modelled and has no material, so a run leaves it
out; the picture puts it back and frames it whole. An SVG carries each picture as an <image>; a PDF stays vector apart
from the pictures. A section (--section) and a field plot's context geometry draw none: a plane cut edge-on has no
area. A file that cannot be read is drawn as a checker with a red cross, as the view draws it.
A worked example, from an empty folder
Two commands to set it up, three questions, one picture. The point of the sequence is that the three questions are what make the last command writable: you cannot name a layer or size a window without first asking what the document has.
$ mkdir work && cd work
$ circuitrf new workspace Amp --tech pcb-2layer_FR-4_70mil_1oz
$ circuitrf new cell Amp Stage1 --views layout
That is a correct, empty layout. Give it some artwork — a 20 mm line on the top copper, a ground plane under it, and a label on the top silk:
{
"FormatVersion": 1,
"DbuPerMicron": 1000,
"DisplayUnit": "Um",
"Shapes": [
{ "$type": "Rect", "Layer": { "Layer": 1, "Datatype": 0 },
"X1": 0, "Y1": 0, "X2": 20000000, "Y2": 2900000 },
{ "$type": "Rect", "Layer": { "Layer": 2, "Datatype": 0 },
"X1": 2000000, "Y1": -3000000, "X2": 18000000, "Y2": -1000000 },
{ "$type": "Label", "Layer": { "Layer": 5, "Datatype": 0 },
"X": 400000, "Y": 3400000, "Text": "STAGE 1", "Height": 500000 }
]
}
Which cells are in here, and which views does each have?
$ circuitrf explain Amp --cells
Amp (workspace)
workspace /home/you/work/Amp/.cws
via nearest ancestor .cws
cells: 1
Stage1 /home/you/work/Amp/Stage1
schematic no-view (none)
symbol no-view (none)
layout sole-file Stage1.clay
What may I ask for with --layers? The technology defines ten; this document uses three.
$ circuitrf explain Amp/Stage1/layout/Stage1.clay --layers
layers PCB 2-Layer FR-4 (70mil, 1oz) — 10 defined, 3 used
Top Copper 1/0 #c87a3e solid purpose=drawing 1 shape(s)
Bottom Copper 2/0 #8a5028 solid purpose=drawing 1 shape(s)
Soldermask Top 3/0 #1e6b3c solid purpose=drawing 0 shape(s)
…
Silk Top 5/0 #f2f2f2 solid purpose=drawing 1 shape(s)
…
How big is it, so I can write a window? In base SI, with the unit and the scale named.
$ circuitrf explain Amp/Stage1/layout/Stage1.clay --extents
extents 0 -0.003 .. 0.02 0.00393846 (0.02 x 0.00693846 m, scale 1E-06)
--window 0um,-3000um,20000um,3938.462um
Top Copper 0 0 .. 0.02 0.0029 --window 0um,0um,20000um,2900um
Bottom Copper 0.002 -0.003 .. 0.018 -0.001 --window 2000um,-3000um,18000um,-1000um
Silk Top 0.0004 0.00336154 .. 0.00331941 0.00393846 --window 400um,3361.538um,3319.414um,3938.462um
The line is 20 mm long and 2.9 mm wide, on Top Copper — and the --window line beside each box is
already in the spelling the next command takes. Now the left 6 mm of it, that layer only:
$ circuitrf render Amp --cell Stage1 --layers "Top Copper" \
--window 0um,0um,6000um,3000um -o stage1-top.png
Wrote stage1-top.png (1600x1200 device-pixels, 10,399 bytes)
1 of 2 shape(s) drawn, 4 vertices emitted, 2 draw call(s)
Three options and one verb, not five commands — because all three questions are the one question
explain already exists for: what did circuitRF decide?
check — is it well formed, does it resolve, is it sound?
$ circuitrf check <path> [--recursive] [--severity warning|error]
Point it at a workspace, a cell folder, or one document. It runs no analysis and it writes nothing, which is what makes it cheap enough to call after every edit — and safe to run on a read-only tree, or on a workspace you have open in the GUI at the same time.
$ circuitrf check ~/designs/Amp
Amp/Stage1/schematic/Stage1.csch
error two labels name one physical net: 'vout' and 'out'
Amp/Stage1/layout/Stage1.clay
warning no technology resolves for this layout
5 document(s): 1 error, 1 warning
Every finding comes from a validator the application already uses — the same view-file check, the
same primacy rule, the same technology walk-up, the same net extractor, the same elaborator, the same
DRC engine. A rule that lived only in check would be a rule the application does not enforce, and a
design would pass here and be refused the moment somebody opened it.
On a workspace, every referenced vendor library is re-read through the
same Validate the Manage Libraries dialog runs. A library that is missing or holds nothing readable is
an error (check.workspace.vendor-library.missing), and so is a part placed in one of the workspace's
schematics that the library no longer holds (check.workspace.vendor-library.part-missing). Files in
the library that could not be read are a warning (check.workspace.vendor-library.unreadable).
A model library, a model folder, a part.cpart or a library.clib is checked
against the model-library format: each model folder gets the cell checks and then its own (check.model.*), and a
library adds one part number in two folders (check.model.duplicate-part). A folder holding model folders is walked to
its full depth without --recursive.
--severity decides the exit code, and it defaults to error. Warnings are
always printed — a check that hid them to keep the exit code clean would make the exit code
useless. Two states are warnings on purpose: a cell folder holding several views with none named
primary, and a layout that resolves no technology. Both are normal.
On a .c3d, a reference image is drawing only, so what is wrong with one is a warning,
never an error — a design does not fail check because a photo moved: an image file that is missing or does not decode
(c3d.image.unreadable, naming the object and the path it resolved to), an image mapped onto a face its object no longer
has (c3d.image.face-missing — the record is kept, never dropped), two images on one face (c3d.image.face-twice), a
face image whose Width or Height is not positive (c3d.image.face-size) and "Locked" on a sheet with no image
(c3d.image.locked-without-image). An object that is not modelled and has no material raises no has no material
warning: only what a run solves is warned of. The realistic view's Look is display too: an
Exposure or Intensity out of range (c3d.look.range), a Background (c3d.look.background) or an Environment
(c3d.look.environment) spelled in no form the view reads is an error, and a .hdr that is missing or does not read is a
warning (c3d.look.hdr-unreadable): the view lights the scene with Studio instead.
A DRC violation says where it is. Each one names the layer as the technology names it, the region in the layout's own display unit, and, for a spacing rule, the two nets and how far apart they are against what the rule needs. Two shapes on different nets that touch are reported as touching, because that is a short rather than a near miss:
$ circuitrf check Filter/layout/Filter.clay
error: Filter.clay: Metal1 Min Spacing (MinSpacing) on Metal1 (1/0) — shapes on nets 'in' and 'sig' touch at (98um, -2um)–(102um, 32um)
error: Filter.clay: Metal1 Min Spacing (MinSpacing) on Metal1 (1/0) — 2um, needs 4um between nets 'in' and 'out' at (-1.728um, 30um)–(101.728um, 32um)
Under --json the same facts are typed arguments: layer, layerName, netA, netB, touching,
measured, required, and the region as minX, minY, maxX, maxY in unit.
On a planar .cem, the mesh budget is part of the result, not only a note: the document's row in
result.check.documents carries em with the kernel, the unknown count, the ceiling it is judged
against (dense or accelerated) and the mesher's verdict (ok, warn or refused). It survives
--summary, so the cheap form of the call still answers whether a run is affordable. A
PlanarMesh field the file states but "Auto": true discards (cells per wavelength, edge cells) is
a warning, check.em.mesh-auto-override, naming the field and the value the mesh uses instead. A
stated edge mesh, cells across the narrowest conductor or detail floor is used as stated beside
"Auto": true (Auto chooses only the rest), and check.em.mesh-auto-kept says so.
The kind of document is inferred from the path, exactly as convert infers a format. A GDSII or
Gerber file is named as interchange rather than called unreadable — it is simply not validated,
because there is nothing to validate it against.
An OASIS file is read. gdstk opens the whole file, checking its CRC or checksum when it carries one,
and check reports its cells, their shape and reference counts, its grid and the layer names it declares
— without importing anything. A damaged file is an error, because the import would refuse it too. On
an installation without gdstk the file is still named, with a warning saying it was not read.
$ circuitrf check mask.oas
note: mask.oas: OASIS, 4 cell(s) (top: top, empty); 1 polygon(s), 0 path(s), 0 label(s), 4 reference(s); 1000 DBU/µm; no named layers. Import it with `circuitrf convert` or File ▸ Import ▸ OASIS (gdstk)….
1 document(s) checked: 0 error(s), 0 warning(s), 1 note(s).
Checking a Touchstone file
$ circuitrf check part.s2p
note: part.s2p: 2-port, 401 points, 1 MHz to 1 GHz, reference 50 Ω.
note: part.s2p: causality not evaluated — the frequency grid is not uniformly spaced.
1 document(s) checked: 0 error(s), 0 warning(s), 2 note(s).
An .sNp is data rather than a design, and that changes the severity rule in one place. Passivity,
reciprocity and causality are warnings and never errors, each carrying the measured number and the
frequency it occurred at: nothing in a Touchstone file says what the part is, so an amplifier is
supposed to have gain and a circulator is supposed to be non-reciprocal. Only the unambiguous
defects — unreadable, a port count that contradicts the file's own name, a frequency axis that is not
sorted, a reference impedance no renormalisation can use — are errors.
A folder walk deliberately skips Touchstone files. A kit directory holds hundreds of them, and measuring passivity and causality across all of them would bury a workspace's own findings under notes about parts you did not author. Naming the file is what checks it.
What each finding means, and the limits of the causality measurement, are on the Derived Metrics page.
explain — what did circuitRF decide?
$ circuitrf explain <path> [--expr "<expression>"] [--set var=expr]
[--analysis [<name>]] [--ref <relative-ref>]
[--cells [--all]] [--layers] [--extents] [--nets] [--view <name>]
[--footprints] [--setup <name>] [--object <name>] [--look]
[--tunables [--snp-entries <SNP3,...|all>]] [--component-ports ["C1,C2"|"*"]]
$ circuitrf explain --pdk <kit> [<workspace>] [--python <interp>]
check answers "is something wrong". explain answers the question that is not a failure: which
technology did this layout get, which analysis would actually run, what does this expression evaluate
to here, what does this cell reference point at, what cells and layers are in here, and how big is it?
Six questions, and they are refused together rather than ordered — one per run. A precedence nobody stated is an invention, and it would answer a question you did not ask while looking like it answered the one you did.
It reports the walk as well as the answer, and that is the useful half. Resolution in circuitRF is
a series of walk-ups — a document's ancestor workspace, a layout's technology, a .cem's two
independent references — and which one produced an answer is exactly what you cannot see from the
file.
$ circuitrf explain Amp.cem
workspace from Amp.cem → /home/you/designs/Amp/.cws (nearest ancestor .cws)
layout from Amp.cem → Amp/Line/layout/Line.clay (workspace-relative)
workspace from Line.clay → /home/you/parts/.cws (nearest ancestor .cws)
technology from /home/you/parts → parts/tech/pcb-2layer.ctech (the .cws DefaultTechRef)
Two different workspaces there, and that is legitimate: a .cem in one workspace may point at a
layout in another, and that layout's layers must be read by its technology.
A 3D view: is each setup solved?
On a .c3d, explain ends with a Solved section: one line per setup and solver, saying whether its last result still
matches the model, whether the run was partial, when it finished, how long it took, and what has changed since. It is the
same answer the editor's setup cards and marks give (3D EM ▸ Is this solved?). --json carries it as
solved.
$ circuitrf explain Launch.c3d
…
Solved
'Palace' FEM (Palace) Solved 14:32 (18 min)
'openEMS' FDTD (openEMS) Out of date: the model has changed
A 3D view: a wave port with terminals
On a .c3d, explain walks every port the way a run resolves it. A wave port met by several conductors
(EM Setup ▸ Several conductors on one face) gets a line for the port —
its face, its reference and why that conductor is the reference — then one per terminal with its number,
conductor, Z0 and voltage path (its ends in the view's display unit), and a last line saying what its
S-parameters are. (Below, the via line under each, which says how it was decided, is left out.)
$ circuitrf explain Pair.c3d
…
port Left touches — region: 'gnd', 'strip_a', 'strip_b'. A wave port with 2 terminals on the air box's xmin face; the reference is 'gnd': 'gnd' is the only one in the ground set.
port Left terminal 1 'P1' conductor 'strip_a', Z0 50 Ω, voltage path (0 µm, 2100 µm, 20 µm) to (0 µm, 2100 µm, 1010 µm)
port Left terminal 2 'P2' conductor 'strip_b', Z0 50 Ω, voltage path (0 µm, 2900 µm, 20 µm) to (0 µm, 2900 µm, 1010 µm)
port Left result terminal S: each terminal is a port of the result (ports 1, 2), its voltage and current on its own conductor, against its own Z0
check reports the same port as one line, and each refusal — a terminal on the reference, two terminals on
one conductor, a number another port already has — names the port and the terminal.
When the setup runs on openEMS (or both solvers), an openEMS wave ports block says how openEMS builds
each wave port (EM Setup ▸ Wave ports): per face, the feed's length and what
set it, how far the grid grows past the face, the source plane and the three voltage planes; per terminal,
the source's shape, the two current planes, the current loop and how many cells it clears the nearest
other conductor by; then the run's own notes about that face (its PML, PEC side walls). A port openEMS
cannot build says a run would stop here: and why.
When it runs on Palace (or both solvers), a Palace terminal ports block gives one line per port with
terminals (EM Setup ▸ On Palace): its terminals as the modes
of one Palace wave port on its face, which entry is Active, the one MaxSize every entry shares, and
that a run refuses the face, before its 3D solve, if its modes travel at one speed. A port Palace refuses (three
terminals, a reference plane moved off the face) says a run would stop here: and why; on both solvers it
says Palace will be skipped.
A 3D view: --object — how does this object look?
On a .c3d, --object <name> walks the appearance the realistic view draws that object
with: one line per field, each saying which statement decided it — the object's own, an instance's (innermost first),
its material's, the material's colour, or the role's default. The name is a top-level object's, which answers for every
solid it elaborates to, or an elaborated one (U1/trace). Nothing in it is read from εr or σ: an appearance is drawing
only. --look lists the Look itself and every appearance in the view the same way.
--analysis — which chain would run
$ circuitrf explain pa.cnl --analysis
SWEEP1 parametric_sweep enabled runnable root → dispatched by hb
chain: SWEEP1 → HB1
sweep: Pavl over 1.0000e+09 … 3.0000e+09 step 5.0000e+08 Hz (stated GHz, scale 1e9)
HB1 hb enabled runnable → promoted to SWEEP1
A sweep is reported in base SI with its unit and its scale. Reading a mark without its scale has already produced a run at 2 Hz that looked entirely normal.
runnable is two claims, not one: the chain reaches an enabled analysis, and every reference the
analysis names resolves — the tuner instances, the inner analysis a sweep wraps, the swept variable,
and the variables a tone expression reads. When one does not, the chain is not runnable and the
report says which:
$ circuitrf explain pa.cnl --analysis
analyses:
LP1 loadpull-pursuit LP1 not-runnable
unresolved: LoadTuner=NoSuchTuner: no instance of that name in this document. Its Tuners are: Load.
unresolved: SourceTuner=AlsoMissing: no instance of that name in this document. Its Tuners are: Load.
runnable used to mean only the first half, so the analysis above was reported
runnable and dispatched by lpp while naming two tuners that do not
exist. Whether a thing will run is the question this verb exists to answer, and a caller that
acts on an optimistic yes gets its refusal later, about something it has already been told is
fine.
What explain still does not claim is anything that would need a solve —
"this bench has no bias source" is a property of the solved circuit, and this verb solves
nothing. It reports what it can establish and stays quiet about the rest.
An envelope analysis also lists what its run will record: the labeled nets whose voltages it keeps, and the IProbes it measures power at (see What a run records):
$ circuitrf explain NR20_ACPR.cnl --analysis
analyses:
ENV1 EnvelopeAnalysis ENV1 DISPATCHED by env
records voltages at 3 net(s): Vin, Vout, VDD
measures power at 3 IProbe(s): Iout, Iin, IDC
circuitrf env --probes a,b still runs: the nodes it names join the labeled nets, and the run notes
that Probes is no longer a setting. A netlist line with Probes= reads the same way, and a file
saved again no longer carries it.
A design whose expressions call a distribution — a kit's statistical models, or a variable such as
Rsh = agauss(50, 2.5, 1) — also lists each one, as a process draw (in a global, shared by every
instance) or a mismatch draw (in a cell, one per instance), with the stream that names it:
$ circuitrf explain mc.cnl --analysis
Distributions: 1 process, 2 mismatch — nominal in every run but a Monte Carlo trial
process agauss Rnom
mismatch agauss X1.R
mismatch agauss X2.R
explain on a Touchstone file — what IS this part?
$ circuitrf explain part.s2p
part.s2p (touchstone)
ports 2
sweep 401 points, 1 MHz to 1 GHz, non-uniform
reference impedance 50 Ω
SRF (shunt-through) 22.507906 MHz
|Z| min (shunt-through) |Z| = 5.057 mΩ at 22.387211 MHz, ESR 5 mΩ
SRF (series-through) (nothing)
|Z| min (series-through) |Z| = 796.177 Ω at 1 GHz, ESR 1.268 Ω
The self-resonance and the impedance floor are what a decoupling capacitor is chosen on, and before this verb the only way to see them was to build a schematic around the file and plot it.
Every applicable fixture is reported side by side rather than one being chosen. Nothing in the file records how the part was measured, and seeing all the readings is the fastest way to identify an unlabelled one — above, the series reading finds no resonance at all and puts the floor five orders of magnitude out, so the part is plainly a shunt-mounted capacitor. The equations behind each reading are on the Derived Metrics page.
--expr — evaluate in the design's scope
$ circuitrf explain pa.cnl --expr "Zopt*2" --set Zopt=12.5
Zopt*2 = 25 (real)
Through the one expression engine, in the design's resolved scope — never by substitution — with
--set applied first exactly as a run verb applies it. The kind is reported, never coerced.
--ref — where does this reference land
$ circuitrf explain Stage1.csch --ref ../parts/SOT-23-3
../parts/SOT-23-3 → /home/you/designs/parts/SOT-23-3 resolved
outside this workspace: no
Where resolution fails, that is the answer — a sentence naming what was looked for and where it was looked. You are usually running this verb precisely because something did not resolve.
A vendor part reference reports its walk a step at a time: which
.cws, which library entry and what it stores, and the folder that resolved. A library the workspace
does not name and a library whose folder is missing say different things.
$ circuitrf explain Stage1.csch --ref vendorlib://Caps/capacitors/series-a.lib
reference 'vendorlib://Caps/capacitors/series-a.lib' from /home/you/designs/Amp/Stage1/schematic
resolved → /home/you/libraries/caps/capacitors/series-a.lib (outside its workspace)
via workspace /home/you/designs/Amp/.cws
via library 'Caps' → '/home/you/libraries/caps'
via folder /home/you/libraries/caps
--cells — what cells are in here?
$ circuitrf explain ~/designs/Amp --cells
Amp (workspace)
workspace /home/you/designs/Amp/.cws
via nearest ancestor .cws
cells: 2
Balun /home/you/designs/Amp/Balun
schematic named-present Balun.csch of 2: alt.csch, Balun.csch
symbol no-view (none)
layout missing-named-primary Balun_rev3.clay of 2: Balun.clay, Balun_rev2.clay
Stage1 /home/you/designs/Amp/Stage1
schematic sole-file Stage1.csch
symbol sole-file Stage1.csym
layout sole-file Stage1.clay
It reports the resolution, not a directory listing, and that is the whole reason to run it rather
than ls. The interesting rows are the ones where the answer is not obvious, and there are five
states, never collapsed into fewer:
| State | Means |
|---|---|
sole-file |
One file in the sub-folder; it is primary by being the only one. |
named-present |
Several files, and the cell names one of them. |
missing-named-primary |
Several files, and the cell names one that is not there. A flat contradiction, and the state you most need to see — the name it looked for is printed. |
no-primary |
Several files and none named. Not an error; nothing has chosen yet. |
no-view |
The sub-folder is empty, or there is none. |
Each is listed with its state — never omitted, and never quietly resolved to the alphabetically first file, which is the answer that would look right and be wrong.
Point it at a cell folder and you get that one cell in the same shape, which is what makes it
compose with render: ask what views a cell has, then render one. --all includes generated cells,
which are hidden by default exactly as the project tree hides them. This is the same enumeration
render --cell resolves through, so a cell listed here is a cell that verb can draw.
--layers — what may I ask for?
$ circuitrf explain Stage1.clay --layers
technology /home/you/designs/Amp/tech/pcb-2layer_FR-4_70mil_1oz.ctech
via the workspace's DefaultTechRef — the layout states none
layers PCB 2-Layer FR-4 (70mil, 1oz) — 10 defined, 3 used
Top Copper 1/0 #c87a3e solid purpose=drawing 1 shape(s)
Bottom Copper 2/0 #8a5028 solid purpose=drawing 1 shape(s)
Soldermask Top 3/0 #1e6b3c solid purpose=drawing 0 shape(s)
Silk Top 5/0 #f2f2f2 solid purpose=drawing 1 shape(s)
…
Two different sets, side by side, and the second is the one that draws anything. The technology's
layer table is what render --layers will accept; the shape count is what this
document actually has on each. A layer with a colour, a purpose and 0 shapes is a name you may pass
that will produce nothing.
The count walks the hierarchy: a layer used only inside a placed sub-cell counts, and an array
placement counts its shapes once per element. It is deliberately not render --json's
counters.shapesDrawn, which counts only the top-level shapes one frame issued a draw call for.
Two things it cannot tell you, both worth knowing:
- A via is reported on its barrel layer only. A via carries a barrel layer and a landing layer, and the renderer draws the whole annulus on the barrel one — so a landing layer a via field's pads are notionally on can report zero shapes.
- A layer the fallback palette invented is listed too, and marked. A key the document draws on that
the technology does not define is ordinary after an import, and it renders. Omitting those rows
would report a document as drawing on layers it does not and hide the ones it does. The generated
name is a name
render --layersaccepts.
--footprints — what artwork does each part state?
$ circuitrf explain Board1/schematic/Board1.csch --footprints
footprints: 3
C1 smt:0402@N builtin 2 pad(s) / 2 port(s)
via the built-in case table, because the value starts with 'smt:' — generated on demand, not read from a file
→ 0402 (metric 1005) 1.00 x 0.50 mm, density N (nominal) — generated
against technology PCB 2-Layer FR-4 (70mil, 1oz)
U1 ../../Widget9 cell 9 pad(s) / 9 port(s)
via a path, resolved against the document's own folder
→ /work/Board/Widget9/layout/Widget9.clay
S4P1 smt:0402@N builtin 2 pad(s) / 4 port(s) MISMATCH
via the built-in case table, because the value starts with 'smt:' — generated on demand, not read from a file
What it states, what that resolved to, and how it got there. A footprint resolves one of two
ways, decided by the first four characters: smt: is a built-in case size, which does not exist as a
file and is generated on demand; anything else is a path, resolved against the schematic's own
folder exactly as a cell reference is. Which of the two produced the answer is printed, because that
is the half you cannot work out from the result.
The technology is named for a built-in and only for a built-in. A generated land pattern picks its copper, soldermask and silkscreen by role out of whatever technology is in force — and the shipped technologies disagree about every layer key, so which technology that is is part of what the artwork will be. A cell you imported or drew already has its artwork on disk on keys of its own, and printing a technology beside it would suggest it was about to be re-resolved.
Pads and ports are on the same line, and a mismatch says so. A land pattern with two pads under a four-port part is a design error that Update Layout reports and refuses to place; two numbers in two places is how that goes unread.
Footprint is artwork, not a value, and it is dropped before parameters are resolved —
it never reaches the simulator. Asking the netlist about it would report every design as stating
none.
--tunables — which values can be tuned?
$ circuitrf explain Bench/schematic/Bench.csch --tunables
Tunables (9):
R1.R top 50 Ohm 10 Ohm .. 200 Ohm [tune; opt]
X1.Rbias top 1 kOhm 500 Ohm .. 2 kOhm [tune]
Rload top 50 Ohm 25 Ohm .. 100 Ohm [opt]
real(Zsrc) top 40 Ohm 20 Ohm .. 80 Ohm [part of 40+15j Ohm]
imag(Zsrc) top 15 Ohm 7.5 Ohm .. 30 Ohm [part of 40+15j Ohm]
mag(Zsrc) top 42.72 Ohm 20 Ohm .. 80 Ohm [tune; part of 40+15j Ohm]
phase(Zsrc) top 20.556 deg -45 deg .. 45 deg [tune; part of 40+15j Ohm]
DUT:R3.R DUT · ×2 10 Ohm 5 Ohm .. 50 Ohm
DUT:R5.R DUT · ×2 22 Ohm 11 Ohm .. 44 Ohm [read-only: it belongs to another workspace]
Every value of the design that can be tuned or optimized, at any depth, with the key a tune line
or a preset names it by. A value is tunable when it is written as a plain number with an optional
unit; an expression is not, and the variable it reads is. A value inside a cell is named
Cell:Instance.Parameter and moves in every instance of that cell — the second column says how
many share it. A sub-circuit instance's own parameters are listed once per instance (X1.Rbias), even
when the instance is using the cell's default. A complex value written with numbers only is listed as
its four parts — real(Zsrc), imag(Zsrc), mag(Zsrc) and phase(Zsrc), the phase in degrees — each
tuned on its own; a preset still holds the whole value under its own key (Zsrc=45+10j Ohm).
A placed vendor part is one key, C1.Part, shown as its part
number, value and description, with a second line giving how its candidates are chosen (kind=same, or
parts="…" and any value bounds) and how many there are, their value span and series. The --json
row carries the same as part. Its own subcircuit's values are not listed: the part is chosen whole.
The range is the one the schematic's tuning setup gives the value, or the range it would get when you first tune it. A value Push cannot write — in a library cell, or another workspace — says why; it can still be tuned and optimized. Keys the setup names that match nothing in the design are listed last, and ignored.
An S-parameter block offers two keys per entry — its offsets SNP3.SdB_i_j and SNP3.Sdeg_i_j, 2·N² of
them — and they are folded into the block's one summary line, so a 15-port board does not list 450 rows
ahead of the parts you asked about. --snp-entries SNP3,SNP4 lists the named blocks' entries, and
--snp-entries all lists every block's.
--component-ports — which ports would a run build?
On a .c3d or a .cem, --component-ports lists each component port as a run
would build it — its pads, conductors, gap, width and verdict, and on a 3D setup its landings — without solving.
On a Planar .cem it describes the strip the solver adds across the part's gap and the delta gap that cuts it. With no
list it reports the setup's own; --component-ports "C1,C2" (or "*") reports that list instead, as
em --component-ports would run it.
--pdk — which interpreter does a gdsfactory kit use?
explain --pdk <kit> [<workspace>] says which Python interpreter a gdsfactory kit
resolves to, and whether the answer came from --python <interp> or from your recorded preference, then
the Python and gdsfactory versions that interpreter reports. <kit> is the module name or the kit's folder
name; with a workspace, the kit's declaration, location and recorded and installed versions are listed
first. It runs no kit code: the only process it starts is the interpreter answering its own version.
--nets — which nets does this layout name?
$ circuitrf explain Board/layout/Board.clay --nets
nets 2 named net(s). Counts shapes drawn in this cell, not copper inside its 17 placed parts.
via the cell's own shapes
nets: 2
+3V3 11 BOT, IN3, TOP
GND 1 GND
The rows of the Nets panel: each net's name, how many shapes carry it, and the layers
they reach, in natural order so N2 comes before N10. --json gives them as the list result.explain.nets. It takes a
.clay or a cell folder with a layout, and any other document is refused by kind. A layout that names no net says so and
exits 0.
--extents — how big is it?
$ circuitrf explain Stage1.clay --extents
extents 0 -0.003 .. 0.02 0.00393846 (0.02 x 0.00693846 m, scale 1E-06)
--window 0um,-3000um,20000um,3938.462um
Top Copper 0 0 .. 0.02 0.0029 --window 0um,0um,20000um,2900um
Bottom Copper 0.002 -0.003 .. 0.018 -0.001 --window 2000um,-3000um,18000um,-1000um
Silk Top 0.0004 0.00336154 .. 0.00331941 0.00393846 --window 400um,3361.538um,3319.414um,3938.462um
In base SI, with the unit and the scale named — the rule --analysis already follows, for the
reason it follows it: reading a mark without its scale has already produced a run at 2 Hz that looked
entirely normal. A schematic or a symbol reports design-units at scale 1, because its coordinates are
dimensionless and dressing them up in metres would be a lie.
This is the same function render --fit frames on, which is why you can size a --window from it
and get exactly the region you expected. The per-layer boxes are the document's own shapes; an
instance's extent arrives as one box for the whole placement.
--window line is meant to be pasted
Every box comes with the same numbers written the way
render --window takes them — in this document's own display
unit, whole document and per layer, and in --json as a window field beside
the coordinates. The metres above are deliberately not what you type: a layout coordinate
carries a unit there, and m is not one of the suffixes it reads. Copy the
--window line and you get exactly that box; there is no conversion to do and no chance of
being three orders of magnitude out.
So framing on one layer is a copy rather than a calculation — and if you want to frame on
one layer while still drawing the others, that is
render --fit-layers.
An empty document says so rather than reporting a zero box, because a zero box is a point at the origin and that is a different fact. On a symbol, or on a layout carrying a fixed-size ruler, the report adds a note: the fit adds room for marks that are drawn in pixels — a pin's name, a ruler's readout — which have no world extent until a page size is chosen. Those marks are why a fitted symbol page is wider than the box reported here.
read — a result, or a document, back
$ circuitrf read <path> [--only a,b] [--group g] [--json]
The inverse of a run verb. A .npy, a Touchstone file or a binary simulation dataset (.ds) is loaded
back into cubes — through the same loaders the Data Display's source library reads a file with — and one
of circuitRF's own
documents comes back as its own bytes, unchanged.
$ circuitrf read results/Amp_em.npy
results/Amp_em.npy (npy)
group (default):
S Complex freq[201] Hz x i[2] port x j[2] port
Z0 Complex port[2] port
group planar:
MeshCells Real cell[1544]
A .ds lists the cubes a trace can name — a uniform PortZ read as Z0, and the Z and Y it
brings — and then a note: for anything not read as plainly: text variables, or a PortZ that varies.
A file that is not a full grid is refused with the reader's own sentence.
It writes nothing, and it takes one file at a time. For "what is in this workspace", use check or
explain; for a GDSII or a Gerber set, use convert.
With --json, --only and --group narrow what comes back — which matters, because reading a
20,000-point swept loadpull in full is the expensive direction:
$ circuitrf read hero3.npy --only Pout_dBm --json
netlist — the netlist a schematic runs as
$ circuitrf netlist <path.csch | cell-folder | workspace --cell N> [-o out.cnl]
$ circuitrf netlist <path.cnl> --to-schematic [-o out.csch]
The extraction Simulate performs, as a file you can read. It takes a .csch, a cell folder, or a
workspace with --cell — the same three inputs render takes, resolved the same way.
$ circuitrf netlist Example_SParam_LC.csch
; extracted from Example_SParam_LC
Port:Term1 n1 0 Num=1 Z=50 Ohm
L:L1 n1 n2 L=2 nH
C:C1 n2 0 C=0.8 pF
Port:Term2 n2 0 Num=2 Z=50 Ohm
analysis SP1 type=sparam start="1" startUnit=GHz stop="5" stopUnit=GHz npts=201
Without -o the netlist goes to stdout, so circuitrf netlist Stage1.csch > stage1.cnl is the file
and nothing else. With -o it is written there, and the path is what goes to stdout.
You do not need it to simulate a drawing: every run verb takes a .csch directly and
extracts it in memory. What this verb is for is seeing that extraction — and checking a
.cnl you wrote by hand against what circuitRF produces for the equivalent schematic.
Both go through one function, so the file here is not merely equivalent to what a run reads, it is
byte for byte the same text.
-o takes a .cnl and refuses any other extension — there is one format here. A .cnl input is
refused too, unless you ask for a drawing of it: passing it through the
reader and the writer would hand back a file that is not the one you gave (comments gone, directives
reordered) and call it an extraction.
Drawing a netlist: --to-schematic
$ circuitrf netlist filter.cnl --to-schematic -o filter.csch
filter.csch
The other direction: a .cnl you wrote becomes a schematic you can open, review and edit. The
drawing puts the signal path left to right, from port 1 to port 2, with the ports at its ends. Each
element hanging off a net on that path (a shunt capacitor, a termination, a port) drops below the
net. An element bridging two nets on the path sits above it. Everything else goes in rows underneath,
near what it connects to. Every terminal on net 0 gets its own ground symbol, every wire is
horizontal or vertical, and every net carries a label with the name the netlist gave it. Globals
go in one VAR block and measurements in one MEAS block, above the circuit. The analyses go into the
schematic as they were.
It extracts back, through the same extraction Simulate performs, to the same instances, the
same nets in the same order, and the same parameter values. The one thing a drawing may add is a port
count a variadic symbol needs to draw its pins (NumPorts on an SDD or a Z-port), when the
line left it to be inferred from its nets. Run circuitrf netlist filter.csch on the
result to see for yourself.
A connection the router cannot draw without crossing something is made by net label — a real connection, so the circuit is unchanged, and the verb names the nets it did that for. A netlist it cannot draw is refused, with every cause named and nothing written: one that defines cells (a hierarchy needs a cell folder per definition), an instance whose type has no schematic symbol, or a wirebond, whose pins come from the design file it references. User functions and directives the reader keeps verbatim have nowhere to go in a schematic. They are left out, and the verb says so.
A Touchstone File reference is written relative to where the drawing is saved, so it still finds
the same file. Without -o the drawing goes to stdout with its references relative to the netlist's
own folder, which is right for circuitrf netlist filter.cnl --to-schematic > filter.csch.
plot — a picture of a result
$ circuitrf plot <result.npy|.sNp> -o <out.svg|.pdf|.png> --trace <spec> [--trace <spec>]…
One plot, one axis pair, without writing a data display first.
$ circuitrf plot lc.s2p -o match.png \
--trace cube=S,i=1,j=1,y=db20 --trace cube=S,i=2,j=1,y=db20 \
--title "LC lowpass" --ylabel dB --x 1:5 --y -40:5
Wrote match.png (792x612 device-pixels, 19,785 bytes)
1 plot(s) on 1 page(s), 1 data source(s)
A trace spec is comma-separated key=value:
| Key | What it means |
|---|---|
cube |
Which cube. Required. It is the same shorthand the trace card's spec box takes, so S, S[:,2,1], Pout and mag(V[:,"X1.drain"]) all work, and so does plotting against another quantity: cube=Gain[~,:] vs Pout_dBm draws gain against output power, one curve per value of the outer sweep. An X side that does not resolve is refused (plot.trace.unresolved). |
i, j |
The port numbers of a matrix cube — i=2,j=1 is S21. Refused together with a bracketed slice: they are the convenience over writing one. |
y |
db20 (20·log₁₀ — S-parameters, voltages, currents, a probe margin), db10 (10·log₁₀ — powers), mag, phase, real, imag or conj. A bare db is refused as ambiguous (plot.trace.db-ambiguous): in a data display it means 10·log₁₀, while a measurement's dB() is 20·log₁₀, and the wrong one draws an S-parameter at half its depth. |
axis |
left (the default) or right. |
label |
The trace's own name, quoted when it holds a comma: label="As built, rev B". It replaces the generated name in the legend, the axis title and the marker box, exactly as the trace card's Label box does, and --write-cdd writes it. |
cut |
An antenna pattern cut: a bearing in degrees pins the cube's phi axis and sweeps theta; all keeps every phi as a curve family. The verb prints the φ it landed on. |
port |
The port number on a cube's port axis — not an index. A port the run does not hold is refused, listing the ones it does. |
freq |
Pins a freq axis to its nearest sample. Takes an SI suffix: 2.45G. |
probe |
A WSProbe label. Given, it turns a cube=<analysis>.wsp trace into a probe metric. |
metric |
Which of the reference document's quantities — H0, 1/Y0, ZG, SM_Y0, LGa, SMenv, … See The WSProbe. Some take with=, set=, z0=, side=, gi= or the envelope's grid keys. |
circuitrf plot amp.npy -o margin.svg --trace cube=SP1.wsp,probe=GATE,metric=SM_Y0,y=db20
Case is load-bearing in that notation and is not folded away — LGF is one probe's forward
synthetic-circulator loop gain and LGf is a probe pair's feedback-as-synthetic-FET loop gain.
A name whose canonical form is shared by two quantities resolves only when it is spelled exactly.
circuitrf read <result> lists the cubes a file holds and their axes, which is where the names come
from. A cube the file does not hold is refused, listing the ones it does.
| Option | What it does |
|---|---|
-o <path> |
Required. Its extension picks the format: .svg, .pdf or .png. |
--type rect\|smith\|polar\|table |
Default rect. |
--freq-unit Hz\|kHz\|MHz\|GHz |
Default GHz. It is also the unit --x is read in. |
--title, --xlabel, --ylabel, --y2label |
Custom labels. Omitted, the plot labels itself. |
--x lo:hi, --y lo:hi, --y2 lo:hi |
Axis windows. An axis you leave out autoscales. Refused on a Smith or Polar chart, whose window is the complex plane. |
--size WxH, --scale, --dpi |
The page. Default 792×612 points — the same page File ▸ Export writes. --scale/--dpi are .png only. |
--variant light\|dark, --background opaque\|transparent |
As render. |
--outline-text |
.svg only: labels as glyph outlines, so no viewer substitutes a font. As render. |
--radial linear\|db |
How a polar plot's radius is read. db makes it an antenna pattern plot. Refused on any other --type. |
--db-floor <dB> |
The centre of a db plot, relative to the outer ring. Default −40. |
--db-ring <dB> |
Ring spacing. Default 10. |
--db-ref peak\|<dB> |
The outer ring: the data's own peak (normalised, the default) or an absolute level. |
--db-unit <text> |
What the radial numbers are in — dBi, dB(W/sr). |
--trace …,ref=<dBm> |
Read a dBm level (TrpDbm, PeakEirpDbm) against this conducted input power instead of the one the run published. An exact dB shift — no re-run. Inert on anything that is not a level. |
--whole-plane |
Draw each cut= as one trace spanning −θmax … +θmax, by fetching the φ + 180° half alongside it — instead of the default two traces per cut. Needs --type polar --radial db. |
--angle-labels |
Print the bearing every 30° outside the disc, with a spoke to each, the way an antenna-range plot is drawn. Polar only, either radial mode. The disc shrinks to make room rather than the numbers overprinting the outer ring. |
--legend off\|inside\|below\|floating |
Where the legend goes. |
--write-cdd <path> |
Also write the data display this drew. |
circuitrf plot run.npy -o eplane.svg --type polar --radial db --db-unit "dB(W/sr)" \
--trace cube=farfield.U,cut=0,port=1,y=db10
# both principal planes, one trace each, with bearings around the rim
circuitrf plot run.npy -o cuts.svg --type polar --radial db --whole-plane --angle-labels \
--trace cube=farfield.U,freq=5.8e9,cut=0,port=1,y=db10 \
--trace cube=farfield.U,freq=5.8e9,cut=90,port=1,y=db10
On a --radial db plot the outer ring is a reference and the centre is a floor, and the
picture states which reference it is using — a 0 dB peak with no reference is not a result.
Values below the floor are drawn AT the floor, never dropped: a gap in a pattern trace reads as
a null in the antenna, and a real null and a clipped value must not look the same. 0° is at the top
and angles increase clockwise, and the note under the plot says what the θ range covers — with an
infinite ground plane there is no field below the horizon, so a pattern occupying part of the disc is
the model saying so rather than a drawing fault.
--write-cdd is how you go further
This verb draws one plot with one axis pair. Everything else a data display can do — several plots
on a page, tabs, markers, contours, a summary table — is still done by writing a .cdd and
calling render. --write-cdd hands you the document
this verb built, which is a correct starting point to edit rather than a blank page; the picture it
draws and the picture render draws from that file are byte for byte the same, because
they are the same code.
A plot with no --trace is refused rather than drawn. An empty plot is a valid picture that exports
cleanly and looks exactly like a measurement that came back empty.
find — what is in this folder?
$ circuitrf find <root> [--depth n] [--no-analyses]
$ circuitrf find <workspace> --parts "<query>" [--library <name>] [--limit 50]
The workspaces under a directory, their cells, each cell's views, and the analyses each cell declares.
$ circuitrf find ./projects
/home/you/projects (1 workspace(s), 2 cell(s), depth 4)
Amp /home/you/projects/Amp [tech tech/generic-mmic.ctech]
LC schematic: LC.csch
analyses: SP1
Stage1 schematic: Stage1.csch, layout: Stage1.clay
analyses: HB1, SWEEP1
A cell with a 3D view also gets a solved: line: each setup's state (current, outOfDate or notRun), and
(partial) where the run was cancelled, interrupted or did not converge. explain has the detail.
It reads what the other verbs read, so a cell listed here is the cell render would draw and the
analyses are the ones hb would dispatch. It writes nothing, so it runs on a read-only tree and on a
workspace the application has open.
| Option | What it does |
|---|---|
--depth n |
How many levels below the root a workspace is looked for. Default 4, at most 12. |
--no-analyses |
Skip the analyses. Each one costs an extraction, which adds up on a large tree. |
Searching vendor libraries: --parts
With --parts, find searches the workspace's vendor part libraries
instead of listing documents. Words are separated by spaces and every word must match. A word matches a
part when it appears in the part's name, its description (its unit brackets dropped, so 50V finds
50[V]), its file or its folder. A word that reads as a value (10p, 10pF, 10 pF, 2.2n, 4n7) matches
the part's stated value within 0.5 % instead, and a unit, when given, must be the part's (10nH
finds inductors only). A case size matches the stated size exactly: 0603 the imperial code, 1608M
the metric one. The palette's search box and the Parameter Editor's Part field use the same rules.
$ circuitrf find ~/designs/Amp --parts "10p C0G"
Caps spice TSTC0402A0018 10 pF ±0.5 pF 1005M(0402)/C0G(-55~125degC)/10[pF]/50[V] 100 MHz – 30 GHz File=vendorlib://Caps/capacitors/series-a.lib Name=TSTC0402A0018 Glyph=C Footprint=smt:0402@N
Caps model TSTCAP0402 10 pF Thin-film capacitor, measured 0 Hz – 20 GHz CellRef=vendormodel://Caps/Passives/TSTCAP0402 Glyph=C Footprint=smt:0402@N
The second column is the part's kind: spice for a part in a SPICE file, model for a
model folder. Each row ends with what to write in a .csch so a part
found here can be placed without another question — the parameters the palette writes when the part is
dropped, Footprint included when the workspace's technology is a board and the part states its case
size in both systems. A spice part is a SpiceModel component carrying those parameters. A model part
is a cell instance: its CellRef is the vendormodel://<library>/<model folder> reference shown, it
carries the Glyph row, and its other rows are the model's own parameters. It runs the model's .cnl.
--json gives each part's kind, and a model's cellRef. Rows are sorted by library, then by value.
At most --limit rows are printed (50 by default), and a note says how many matched in all.
A listing that quietly gave up reads as "the workspace is not here". So when the walk hits
--depth with directories still below it, it says so — a warning on stderr and
truncated: true in the --json document. Raise --depth and ask
again. A directory symbolic link is never followed, so nothing outside the root you named can appear
in the answer.
reference — what may I write?
$ circuitrf reference
$ circuitrf reference netlist
$ circuitrf reference components
$ circuitrf reference components MLIN
$ circuitrf reference analyses
$ circuitrf reference analyses sparam
$ circuitrf reference data-display
$ circuitrf reference technology
They are, in order: the list of topics with what each costs; one page as text; every netlist primitive; just that one; every analysis directive with every key it takes; just that one; and the two document formats nothing else here creates for you.
check tells you that what you wrote is wrong. explain tells you what circuitRF made of it.
This one tells you what you are allowed to write in the first place — the primitive type names,
how many nets each takes and in what order, what its parameters are called and what they default to.
It reads no file, runs nothing and writes nothing. There is no path argument: it answers about no particular design, which is exactly why it is useful before one exists.
A netlist naming a type that does not exist fails at elaboration and says so. A component given a plausible but wrong parameter name does not: the name is ignored, the parameter takes its default, and the run converges and produces a complete-looking answer to a different circuit. Getting the spelling from here rather than from memory is what avoids that.
The topics
With no arguments you get the list, with each topic's size — because reading is the expensive direction and a 4 kB page and an 84 kB one should not look alike:
$ circuitrf reference
Reference topics — circuitrf reference <topic>
netlist 17.7 kB The Netlist (.cnl) Format
expressions 9.1 kB Expressions
units 10.9 kB Units
measurements 5.8 kB Measurements
pins-ports-terms 4.3 kB Pins, Ports & Terms
sdd 12.2 kB The SDD (Symbolically-Defined Device)
file-formats 11.6 kB File Formats
component-notes 74.2 kB Components
data-display 12.8 kB The .cdd data-display format
technology 6.1 kB The .ctech technology format
analyses 8.5 kB Analysis directives
tuning 3.8 kB Tuning directives
goals 2.7 kB Optimization goals
statistics 8.7 kB Monte Carlo, yield and corners
components 91.3 kB Component types
circuitrf reference components <TYPE> one primitive
circuitrf reference analyses <TYPE> one analysis directive
These are the same pages you are reading now, shipped inside the program so they are there on a
machine that has no copy of this site. components is the generated catalogue — read from the
live component registry every time you ask, so it cannot go stale — while component-notes is this
site's Components page, which explains what each part is for. The catalogue
answers "what may I write"; the page answers "what does it mean". Ask for both if you want both.
The list above is abridged; the program prints every topic. Three more generated ones answer the questions a design starts with:
| Topic | What it lists | Name one |
|---|---|---|
component-index |
every type token in one line — its nets, its category and what it is. A few kB, against the full catalogue's ~100 kB: read it first, then components <TYPE> |
— |
technologies |
every technology that ships, by the id new workspace --tech takes, with its stackup top to bottom, the layers drawn on each entry and the material libraries it names |
technologies <id> |
shipped-materials |
every material in the libraries that ship — role, εr and tanδ or σ₂₀ and α₂₀, thermal conductivity and source — and how a technology uses one | — |
examples |
every example workspace that ships, with what it shows and where it is | examples <name> |
Like components, all four are generated from the data they describe every time you ask: the
technologies are read from the embedded .ctech files through the same reader as your own, the
materials from the embedded .cmat libraries, and the examples from the index that
File ▸ New ▸ Examples reads.
The example workspaces
reference examples lists the worked designs that ship with circuitRF, the same ones
File ▸ New ▸ Examples offers. reference examples <name> gives one example's README (what each
bench shows and the order to run them in), every file in it, and its cells with the analyses each
declares. Copy one with new example to run it or change it. An example added to
circuitRF appears here as soon as it is in the menu; nothing else lists them.
Asking for a topic prints it as its own Markdown, so it redirects cleanly:
$ circuitrf reference netlist > netlist.md
The component catalogue
$ circuitrf reference components MLIN
MLIN
nets: 2
terminals: 2 1 2
order: The two terminals are interchangeable: the line is uniform, so neither end is the input.
Mlin (MLIN) — Microstrip search: MLIN, microstrip, microstrip line, line, hammerstad
terminals: 2 1 2
W 2.9 mm shown
L 10 mm shown
SignalLayer - - -
GroundReference - - -
The columns are the parameter's name, its default expression, its unit, whether it shows on the schematic by default, and — where the registry has one — what it means.
terminals is the part you cannot get from a picture: the pins the symbol draws, in the order a
netlist line writes their nets. That order is a contract the models themselves read, and it is not
always the one you would guess — a MESFET is gate, drain, source while a JFET is drain, gate, source,
and a diode is anode then cathode.
How many nets the instance line binds is nets. How many pins the symbol
draws is terminals. They are usually the same and they are not always: a
Tuner draws one pin and its line takes two, because its reference terminal is implicit
on the glyph. So do Port, Term, Vdc and IProbe;
a 2-port SDD takes four nets, as ± pairs.
$ circuitrf reference components Tuner
Tuner
nets: 2
terminals: 1 1
Writing Tuner:T1 n_drain Z[1]=50 BiasTee=on Vbias=48 — one net — is now refused by
name. It used to run: the bias tee delivered nothing, every diagnostic was clean, and the output sat
at the engine's floor at every drive point.
An SDD's, a Z_Port's and an SnP's follow a port-count parameter; a
Verilog-A model's follows Pins; an ideal switch's follows Throws; a wBond's
follows the arrays it places. Those report the rule rather than a number:
$ circuitrf reference components SDD
SDD
nets: SddPortCount=N binds 2N nets.
terminals: set by NumPorts — at NumPorts=2: 1+ 1- 2+ 2-
Note that the two lines name different parameters, and that is not a mistake: the parameter
panel calls it NumPorts, and a .cnl instance line spells it
SddPortCount. A number printed where the honest answer is "it depends" is worse than no
number at all.
Analysis directives
$ circuitrf reference analyses sparam
sparam also: sp, s_param, sparameter, s_parameters
bare words: log
type required The analysis kind. One of the tokens this page lists.
enabled true false skips the analysis at run time without deleting it.
start required First frequency. Bare number in Hz unless a unit follows or Unit=/startUnit= is given.
stop required Last frequency, same spelling rules as start.
step 1e8 Step size. Mutually exclusive with npts; npts wins when both are given.
npts - Point count. Selects the point-count sweep mode.
Unit - Sets startUnit, stopUnit and stepUnit at once. Any one of those given individually overrides it.
...
Every analysis type= token, every other spelling of it that is accepted, and every key it may carry
with its default and whether it is required. It is read from the same table the .cnl reader
validates against, so a key that is not on this page is a key the reader refuses — it is not
ignored, and it has not been since the netlist contract landed.
The two formats nothing creates for you
circuitrf reference data-display and circuitrf reference technology describe the .cdd and
.ctech files field by field, generated from the types their readers actually deserialise into, each
with a minimal example that was written out and run. They are here because those are the two formats
you may have to write by hand: new workspace copies a technology in for you, but nothing creates a
data display.
A few entries carry a note instead of a clean answer, and the note is the useful part. GND,
VAR, MEAS and Pin are schematic elements the netlist extractor consumes rather than components
you can place in a .cnl. Chain, ExtDevice, SemiC, Short, Term, V_nTone and I_nTone are
the other way round — writable in a .cnl, with no palette tile and so no declared defaults.
Unknown names are refused with the real list, never guessed at:
$ circuitrf reference components MLINE
No primitive type 'MLINE'. Types: Amp, Atten, BJT_NPN, BJT_PNP, Balun, Bead, C, Chain, …
With --json the whole catalogue comes back structured — type token, terminals, and every
parameter with its default, unit, dimension and visibility:
$ circuitrf reference components --json | jq -r '.result.reference.components[].type'
plugin — a plugin, with no window
$ circuitrf plugin list [--json]
$ circuitrf plugin describe <id> [--json]
$ circuitrf plugin run <id> [--set name=value …] [--workspace <dir>] [--context <path>] -o <dir>
$ circuitrf plugin run <id> [--set …] --place <workspace> [--cell <name>] [--replace | --new-name <name>]
$ circuitrf plugin deny <id>
$ circuitrf plugin install <folder | link | id> [--tag <t>] [--link]
$ circuitrf plugin uninstall <id> [--version <v>]
$ circuitrf plugin update <id>
$ circuitrf plugin rollback <id>
Anything a plugin's panel does, this does with no window: the same process host, the same parameters, the same placement. It holds no plugin logic of its own.
listreads manifests only — nothing is started: id, version, kind,installedorlinked, whether it isallowed,deniedornot asked, and why one that cannot run here cannot.describeprints the manifest as circuitRF read it: every parameter with its kind, unit, default and range, the panel's views, and how it runs on this machine.runstarts the plugin, sendshelloin headless mode and oneupdatewith the parameters, and with **`-o`** writes each view as a file: a plot or Smith chart as its data and a `.cdd` (which [`render --data`](#render-cdd) draws), a layout as a `.clay`, a schematic as its `.cnl`, a table as CSV in base SI, an image as itself, and the readouts as `readouts.json`. The readouts are also printed; the plugin's messages go to stderr. **`--place `** then sends `commit` and places the cell exactly as **Place in workspace** does — validated, behind a restore point. A cell name that is taken is a refusal naming `--replace` and `--new-name`. --set name=valueis what you would type in the panel: a quantity with an SI prefix or an expression (--set f0="2.4 GHz"), a choice's option, a substrate, technology, cell or result by its picker text or a short name that picks one. A parameter you do not set starts where the panel starts it. An unknown name lists the parameters; a value out of range states the range.--workspaceis the workspace the parameters resolve against (by default--place's);--context <path>is where the plugin is told it was opened from — a cell folder, a.clay, a.ctechor a result.- There is no
allow. Permission to run someone else's program is given only in circuitRF's window.denytakes it away. A plugin not yet allowed is refused withplugin.permission.needs-user, which says only you can allow it, and where. installcopies a folder in (--linkruns it in place instead), installs a GitHub or GitLab release — the release's asset for this platform, else its source archive (--tagpicks one) — or installs a version from the vetted list, checked against its SHA-256. An install never allows the plugin and, from here, runs nothing — a Python plugin's requirements are installed once you allow it in the window.uninstall,updateandrollbackdo what Settings ▸ Plugins' buttons do.
A window plugin runs here only if it declares a headless form (Writing a Plugin).
CRF_NO_PLUGINS=1 turns every plugin off. Exit codes: 0; 1 for a refusal, a plugin error message or a failure; 130 when
cancelled.
elab — the elaborated netlist
$ circuitrf elab <file.cnl|.csch>
Elaborates and stops: flattens the hierarchy, resolves every parameter and expression top-down, and numbers the nodes, then prints the elaborated netlist — the exact thing the engines consume. No analysis runs.
This is the debugging verb. When a value is not what you expected, elab is where you find out
whether the expression resolved to something different from what you meant, or resolved correctly and
the analysis is doing something else.
--json — one machine-readable document
Every verb takes --json, spelled that way everywhere. It changes exactly one thing: stdout carries
a single JSON document and nothing else.
stderr is untouched — progress, [circuitRF] notes, warnings and refusal sentences stream exactly as
they always did — so a script watching stderr cannot tell whether the flag was passed.
$ circuitrf sparam amp.cnl --json | jq '.outputs[].path'
"amp.s2p"
One schema serves every verb:
{ "circuitrf": { "version": …, "verb": … },
"input": { "path": …, "analysis": … },
"status": "ok" | "not-converged" | "failed",
"exitCode": 0 | 1 | 2 | 130,
"outputs": [ { "kind": …, "path": … }, … ],
"diagnostics": [ { "id": …, "severity": …, "message": …, "arguments": { … } }, … ],
"result": { … } }
input.analysisis the chain that actually ran, after promotion — not what you asked for. The difference is a whole sweep axis, so you must be able to see it from the document alone.- A failed run still emits a document. The failure is the payload, so you never have to tell "no output" apart from "output I could not parse".
- The diagnostic
idis the contract; themessageis not. Match onid. Templates are reworded freely, and the sentence is always English and culture-invariant. resultholds cubes (groups) for a run, a summary forlp/lpp— the same one-row-per-Γ- point projection the table prints, and--alladds the cubes — a check or explain report for those two verbs, a document forread, a reference report forreference, and a render report forrender.result.renderis everything the picture cannot say about itself:viewport(withletterboxed), the document'sextents, thesize, thethemeand which step of the chain resolved it, thelayerswith whether each was drawn and how many shapes the document has on it,detailwith its effective tolerance,counters, andbytes. Extents and viewport come back in base SI with the unit and the scale named. A.cddaddsdataDisplay, naming every source and the file it resolved to. There is no duration —countersis a work count, deterministic and machine-independent, so it is something a build can assert on; a wall clock is not.result.explaingainscells,layersandextentsfor the three questions of the same name.extentscarriesperLayerboxes beside the whole;layerscarries the resolved technology and the walk that found it.result.shapeis always there, on every run and everyreadthat produced cubes, whether or not the values are: the groups, the cube names, each cube's kind and unit, how many numbers it holds, and every axis with its name, unit, length and end points. It is what lets you find out what a run produced before deciding what to ask for, and it costs a couple of kilobytes on any result.result.narrowedappears when you used--ator--range, and says per axis what you asked for, what you got, and whether it was thenearestgrid point orinterpolated.- Numbers are raw, invariant and unrounded.
NaNand infinity are written as JSON's named literals, because a loadpull grid genuinely contains NaN wherever a point never converged.
Every cube says what its numbers are in
A cube's unit is always present and never empty. SI symbols as they are written — Hz, V, A,
W, Ohm, F, H, K, m, s — and dB and dBm likewise; % for a ratio already scaled to a
percentage, 1 for one that is not, index for a flag or a count, and unknown where circuitRF
cannot say. A measure line you wrote yourself has whatever unit your expression has, and saying
unknown is an answer where a guess would not be.
One loadpull-pursuit result carried Efficiency reading 65.84 and MXE_Eff
reading 0.7087 at the same operating point — the cube in percent, the scalar as a fraction. Reading
the scalar and formatting it as a percentage gives 0.7%; multiplying the cube by 100 gives 6584%.
Both are one plausible line of code, and until the unit was carried there was nothing anywhere to say
which was which. Nothing was rescaled to fix it: the numbers are what the engine computed, and the
document now says what they are.
--summary: the notes as counts
Some verbs are deliberately talkative. The Gerber import names every inference it made as an
inference, which is exactly what lets you decide whether to trust the result — but it is not what you
want back from convert --list-cells, whose answer is one cell name.
--summary reports the informational notes as counts by severity and adds a diagnosticSummary
block saying how many were left out. Warnings and errors are never collapsed, and stderr still
carries everything, so nothing is hidden: what you stop paying for is thirty notes describing
inferences that all went fine.
--summary changes the diagnostics only, never the result. Anything a caller needs as a number, such
as check's mesh budget on a .cem, is in the result and is the same with or without it.
serve — the MCP server
$ circuitrf serve --root <dir> [--kits <dir>] [--print-config]
serve runs circuitRF as a Model Context Protocol server over its standard input and output, so an
assistant, a design agent or a CI job can drive every verb on this page. Connecting a client, the tools
it offers, what it refuses and how to install circuitRF unattended are on
The MCP Server page.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Ran, and produced something usable |
| 1 | Could not run — bad arguments, a missing file, no matching analysis, a refusal, an exception |
| 2 | Ran, but did not converge |
| 3 | Finished, but a check failed: an opt goal unmet, a yield below its target, a model check-charge above its threshold or a model validate tolerance missed |
| 130 | Stopped — a run cancelled at a work boundary, by em's own stop or by a serve client's cancellation |
2 is deliberately not the same test for every verb. hb and dc fail on any non-converged
solve. A loadpull grid in which some points do not converge is a normal and useful result — the edge
of a Γ grid routinely will not — so lp returns 2 only when every grid point failed, and lpp
only when neither optimum converged and there is no follow-on grid. A rule that failed the whole run
on one bad point would make the exit code useless in a script.
Scripting patterns
Keep the table, keep the log, and still see it run.
$ circuitrf lp hero3.cnl -o hero3.npy > hero3-table.txt 2> hero3-run.log
Sweep a variable the netlist already has, without editing the netlist:
$ for p in -10 -5 0 5; do circuitrf hb pa.cnl --set Pavl_dbm=$p -o pa_$p.npy; done
Fail a build on a regression, using the exit code:
$ circuitrf hb pa.cnl -o out.npy || echo "PA did not converge" >&2
Validate a whole workspace in CI, before anything is run:
$ circuitrf check ~/designs/Amp --severity error || exit 1
Author, validate and simulate with no display at any step — the whole loop:
$ circuitrf new workspace build/Amp
$ circuitrf new cell build/Amp Stage1
$ cat > build/Amp/Stage1/Stage1.cnl <<'EOF'
… your netlist …
$ EOF
$ circuitrf check build/Amp || exit 1
$ circuitrf hb build/Amp/Stage1/Stage1.cnl -o out.npy
Pull one number out of a result, without parsing a table:
$ circuitrf read out.npy --only Pout_dBm --json | jq '.result.groups[""].Pout_dBm.values[-1]'
Ask why a file resolved the way it did, when a run used a technology you did not expect:
$ circuitrf explain Amp.cem
Put a picture of every cell into a build's artefacts, at the size a report reads at:
$ circuitrf explain ~/designs/Amp --cells --json \
| jq -r '.result.explain.cells[].name' \
| while read -r c; do
circuitrf render ~/designs/Amp --cell "$c" --detail screen -o "artefacts/$c.png"
done
Watch a layout change across two revisions, without opening either:
$ git show HEAD~1:Stage1/layout/Stage1.clay > Stage1/layout/before.clay
$ circuitrf render Stage1/layout/before.clay -o before.png --window 0um,0um,6000um,3000um
$ circuitrf render Stage1/layout/Stage1.clay -o after.png --window 0um,0um,6000um,3000um
Two things make those two pictures comparable, and both are easy to lose. The explicit --window:
--fit frames each file's own extents, so a change in size would move everything in the frame and the
diff would be of the framing rather than of the artwork. And the older revision written inside the
workspace: a .clay somewhere else resolves no technology and draws on the fallback palette, so the
whole picture would change colour.
Re-extract every EM setup in a workspace after a technology edit — the layout and stackup references resolve themselves, so the loop needs nothing but the file names:
$ for f in em/*.cem; do circuitrf em "$f" || exit 1; done
Because each of those writes the same file Simulate writes, a schematic that references the extracted Touchstones picks the new results up with no further action.
See also: Simulations (what each analysis computes) · The netlist format · EM Setup · The layout editor · The Data Display · The MoM engine · Results & data export · Kits and external device models.