circuitRF Reference Guide

Expressions

One expression language runs everywhere a value can be computed: global variables, component parameters, SDD device equations, and measurements. Anywhere you can type a number, you can type an expression — `2*pi*freq0`, `polar(0.1, 35)`, `if(Vg > Vth, gm*Vg, 0)`.

Where expressions are used

ConsumerWhat it evaluatesResult
Global variables (VAR)a testbench variableReal or Complex
Cell parametersa parameter default or an instance overrideReal or Complex
SDD device equationsi = f(v), q = f(v) over port voltagesReal (and its derivatives)
Measurementsa figure of merit over result cubesReal or Complex
Same grammar, operators, and functions everywhere; only the available operands differ.

circuitRF parses an expression once into a tree and evaluates it against a scope — it never does text substitution. That makes it fast (an SDD equation is parsed once, evaluated thousands of times) and correct (no fragile longest-name-first variable replacement).

Values: Real, Complex, Bool, String

Every value carries a kind:

Ordering needs real operands

< <= > >= require Real operands (complex numbers are unordered); == / != work on Real and Complex. Real ∘ Complex promotes to Complex.

Operators & precedence

Lowest-binding (evaluated last) to highest-binding (tightest):

OperatorsNotes
?: (ternary)cond ? a : b — same as if(cond, a, b)
||logical OR
&&logical AND
== !=equality (Real or Complex)
< <= > >=ordering (Real only)
+ - (binary)
* /
+ - ! (unary prefix)
^ (power)binds tighter than unary minus: -2^2 == -4; right-assoc: 2^3^2 == 2^(3^2)
function call, ( ), atomshighest

Constants & functions

Constants: j = (0,1), pi, e. These (and all function names) are reserved — a variable may not shadow them.

GroupFunctions
Trigsin cos tan asin acos atan atan2(y,x)
Hyperbolicsinh cosh tanh
Exp / log / powerexp log (natural) log10 sqrt pow(x,y) abs
Miscmin(a,b) max(a,b) sign(x), if(cond,then,else), validrange(x, lo, hi) — x, with a warning when it lies outside lo … hi
Complex → Realreal(z) imag(z) abs(z) mag(z) (= abs) phase(z) (degrees) phase_rad(z) (radians) unwrap(z) — the phase in degrees, continued across ±180° along the frequency axis instead of jumping by 360°; unwrap(p) does the same to a phase p already in degrees, and unwrap(x, "axis") names another axis
Slopederiv(y) — the derivative of y along its axis, per unit of that axis: a swept oscillator's deriv(OSC1.Fosc) is Kvco. A value with several axes takes freq, or deriv(y, "axis") names one
Real,Real → Complexpolar(mag, phase_deg) — e.g. polar(0.1, 10) is 0.1∠10°
Network metricsmu(S) mu_prime(S) K(S) delta_mag(S) max_gain(S) (dB) max_gain_lin(S) passivity(S), each also (S, in, out) for an N-port — S is an analysis's S cube, e.g. mu(SP1.S)
Noisenf(S[, Γs][, port]) (dB) — the noise figure from port 1's Z0 or a source of reflection Γs, to port 2 or port; te(S[, Γs][, port]) (K) — the same as a noise temperature; rn(S) — Rn/R; ns_circle(S, NFdB[, npts]) — the constant-noise-figure circle as {freq, point} Γ points, or ns_circle(NFdB, NFmin, Sopt, rn[, npts]) from noise parameters. The S forms need a run with Noise=yes — the equations
Gain and stability circlesga_circle(S, GdB[, npts]) — the constant available-gain circle on the source plane; gp_circle(S, GdB[, npts]) — the constant operating-gain circle on the load plane; stab_circle(S, "source"|"load"[, npts]) — a stability circle; stab_side(S, "source"|"load") — 1 when the inside is stable, −1 when the outside is. Two-ports; GdB is a number or one per frequency — the equations
Delay / matchgroup_delay(z) (seconds; or group_delay(S, in, out)) vswr(Γ)
Statistics over trialsmean_over std_over median_over pctl_over(x, p) skew_over kurt_over yield_over(cond) cpk(x, lo, hi) sigma_to(x, limit); histogram(x, bins) cdf(x) normq(x) yield_sens(pass, x, bins) — measurement functions over a Monte Carlo run's trials
Distributionsagauss(nom, dev[, k]) normal, σ = dev/k · gauss(nom, rel[, k]) normal, σ = nom·rel/k · aunif(nom, dev) uniform on nom ± dev · unif(nom, rel) uniform on nom·(1 ± rel) · limit(nom, dev) nom ± dev, equally likely — each is nom except in a Monte Carlo trial. limit(x, lo, hi) is a clamp.
phase and the .phase cube transform both use degrees. dB/dBm are measurement functions, not general built-ins (and so are never unit suffixes). So are max_over(x, lo, hi) and min_over(x, lo, hi), the worst value over a band, and value_at(x, v), the value at one frequency — unlike min(a,b)/max(a,b), which compare two values. The network metrics, the noise functions, group_delay and vswr are measurement functions too; a metric reads the analysis's own port reference impedances (and nf its noise waves), so pass SP1.S itself, not a slice of it. A distribution in a global variable is drawn once per trial and shared; in a cell's parameter or variable it is drawn for each instance. A variable holding one is not tunable.

Units

A unit attaches in two places. At the assignment level (after the expression, or in the row's unit column) it scales the value by a linear factor — L = L1 nH, Z = 50 Ohm, M = 0.5 pH. And glued to a number inside the expression, with no space, it makes a unit literal — 10um + 1mil, 2.4GHz, 10V + 100mV — scaled to base SI as it is read. 2 GHz, with a space, is not a literal.

An expression holding a unit literal, or naming a variable declared with a unit, is already in base units, so the assignment's unit is not applied to its result; a bare number added to, subtracted from or compared with such a term takes that unit instead (w + 5 in mil is 5 mil more than w), and a bare number that multiplies or divides stays a number (2*w). See Units.

DomainUnits
SI prefixesT G M k m u n p f (1e12 … 1e-15)
FrequencyHz kHz MHz GHz THz
InductanceH mH uH nH pH fH
CapacitanceF mF uF nF pF fF
ResistanceOhm mOhm kOhm MOhm GOhm TOhm
Lengthmetre mm um cm nm mil in inch — m is the prefix milli, never the metre
Angledeg rad
dB and dBm are not units

They are logarithmic, not linear scale factors, so they are functions — dB(...), dBm(...) — never a unit literal: 0dBm is refused.

Variables, scope & cell parameters

Global variables (authored in a VAR block) are visible everywhere. Cell parameters pass top-down: an instance binds overrides in the parent scope; the cell evaluates its own defaults and component values in its own scope, then passes its scope down to its sub-cells. A name resolves to the local cell variable/parameter first, then the global — a cell never sees a parent's locals or a sibling's, which is what keeps a cell's meaning independent of where it's placed.

freq is reserved

Lowercase freq is the simulator's current stamping frequency (Hz), injected when the engine evaluates a frequency-dependent value (a Z_Port impedance, an SDD H[w]). You can't name a variable freq. It is distinct from a source's capital-Freq tone parameter.

User-defined functions

Define a function whose body is an expression in the same language, with any number of parameters:

gm(vgs, vth) = beta * tanh(vgs - vth)

A call binds arguments positionally into a fresh scope whose parent is the definition's scope (so a user function sees globals, not the caller's locals). User functions compose with built-ins and with each other, and are subject to the same cycle detection.

Cycle detection & errors

circuitRF detects and rejects dependency cycles across the whole graph — global variables, cell-parameter defaults, and overrides — and reports the chain (e.g. a → b → a) rather than hanging. Every error names the offending text, never a silent zero or NaN:

SDD equations are real-only

SDD device equations operate on real time-domain voltages and produce real current/charge, so j is disallowed there and sqrt/log of a negative is a domain error rather than a promotion to Complex. circuitRF differentiates SDD equations automatically (forward-mode AD) to get the solver's conductance/capacitance — including through an if, where it differentiates the branch that is taken. For tough convergence prefer soft switching (tanh) over a hard if. See The SDD.


See also: Measurements · The SDD · VAR component. Full design: docs/design/expressions.md.