circuitRF Reference Guide

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
Windows: 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.

A file that works headless works when opened

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&gt;/dev/null to silence them, or 2&gt;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.

Ask for the part you want, not the whole result

--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.

An option a verb does not take is refused, never ignored

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.

Ports with different reference impedances

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.
Why it is promoted rather than obeyed

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.
The two grid options are refused, not ignored

--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
Author the setup in the GUI; run it from the command line

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 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
Why the default path is not the CLI's to choose

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.

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:

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
A 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

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

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:

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.

A drill file that does not state its format is a refusal, not a guess

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.

Every default is the dialog's

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.

A document with no workspace above it renders

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.

On a layout, every coordinate carries a unit — zero included

--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.

Ask 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.

A source that does not resolve is a refusal, never an empty plot

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
$ 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

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

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.

Warnings are reported and still exit 0

--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.
Changed in 1.0

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:

--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.

It reads the schematic, not the netlist

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.

The --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 &gt; stage1.cnl is the file and nothing else. With -o it is written there, and the path is what goes to stdout.

This is the netlist a run actually consumes — the same bytes

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.

The drawing is the netlist

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
A pattern plot says what it is

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.

The walk is bounded, and it says when it stopped

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.

Why this matters more for a script than for you

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.

`nets` and `terminals` are two different numbers, and the first is the one to write to

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.

Some counts are not fixed, and they say so

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.

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":      { … } }

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.

Why this is worth a field of its own

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.