circuitRF Reference Guide

The Netlist (.cnl) Format

A circuitRF netlist is a human-readable text description of a circuit and what to simulate. It's the same thing the schematic editor produces internally, and it's what the engine actually runs.

What a netlist is, and why you'd use one

Most users never write a netlist by hand — you draw a schematic and press Run, and circuitRF extracts the netlist for you. But the .cnl file is worth understanding because:

Syntax basics: one statement per line; ; begins a comment (to end of line); whitespace separates tokens; values may carry a unit (50 Ohm, 2 GHz, 1 mF); and expressions may reference variables and parameters.

A complete example

This is a single-FET GaN power-amplifier test bench: a harmonic-balance power sweep, swept again over frequency, with measurements for gain, efficiency, and return loss. We'll walk through every line below.

; netlist.cnl — generated from TestBench "HBTest.csch"

define MyFET (gate drain)
  parameters Periphery_mm=1
  Sv = -0.837
  Sc = 0.71
  TV0 = 4.268
  TC = 1.507
  th = 0.001
  a = 0.176
  g = 0.089
  lam = 0.0012
  B = 1130
  SDD:X1  gate  0  drain  0  I[1,0]=_v1/50  I[2,0]=Periphery_mm*(B*TC*tanh(_v2*a*(tanh(g*(TV0 - _v1 + _v2*th + Sc*ln(exp(-(Sv - _v1)/Sc) + 1)))+1))*ln(exp(-(2*TV0 - 2*_v1 +2*_v2*th + 2*Sc*ln(exp(-(Sv - _v1)/Sc) + 1))/TC) + 1) * (_v2*lam + 1))/2
end MyFET

Pin = 0
RFfreq = 2 GHz

C:C1  Vin  n1  C=1 mF
L:L1  n2  n3  L=1 mH
L:L2  n4  Vout  L=1 mH
R:R2  n5  0  R=80 Ohm
C:C2  n5  n6  C=1 mF
P1Tone:P1  n1  0  Pavl=Pin dBm  Z=50 Ohm  Freq=RFfreq  Phase=0 deg  Z[0]=1 Ohm  Z[2]=30 Ohm
Vdc:V1  n2  0  Vdc=-3.05 V
Vdc:V2  VDD  0  Vdc=48 V
MyFET:X1  n3  Vout  Periphery_mm=1
IProbe:Iout  Vout  n6
IProbe:Iin  Vin  n3
IProbe:IDC  VDD  n4
C:C3  Vout  0  C=0.3 pF

analysis HB1 type=hb Tone="RFfreq" ToneUnit=MHz MaxHarm=5 FFTOverSample=1 Tol=1e-6 DriveStepping=IfNecessary GuardHarmonic=0 Lambda=1 MaxIter=100
analysis HB1_sweep_Pin type=parametric_sweep Var=Pin Start=0 Stop=30 Step=1 Inner=HB1
analysis HB1_sweep_RFfreq type=parametric_sweep Var=RFfreq Start=1 Stop=3 Npts=3 Unit=GHz Inner=HB1_sweep_Pin
measure Pin_avail_dBm = Pin
measure Pin_deliv_W = real(0.5*HB1.V("Vin",1)*conj(HB1.I("Iin",1)))
measure Pin_deliv_dBm = 10*log10(Pin_deliv_W*1000)
measure IRL_dB = Pin_deliv_dBm - Pin_avail_dBm; input return loss
measure Pout_W = real(0.5*HB1.V("Vout",1)*conj(HB1.I("Iout",1)))
measure Pout_dBm = 10*log10(Pout_W*1000)
measure Gp_dB = Pout_dBm - Pin_deliv_dBm          ; power
measure Gt_dB = Pout_dBm - Pin_avail_dBm           ; transducer
measure PDC_W = real(HB1.V("VDD",0)*HB1.I("IDC",0))
measure Eff = Pout_W/PDC_W*100
labelednets VDD Vin Vout

Line-by-line walkthrough

1 · Header comment

; netlist.cnl — generated from TestBench "HBTest.csch"

Any line starting with ; is a comment. When circuitRF generates the netlist from a schematic it stamps a provenance line naming the source test bench. Comments are ignored by the engine.

2 · Cell definition (define … end)

define MyFET (gate drain)
  parameters Periphery_mm=1
  Sv = -0.837
  ...
  SDD:X1  gate  0  drain  0  I[1,0]=_v1/50  I[2,0]=Periphery_mm*(...)
end MyFET

A define block declares a reusable cell — here a GaN FET model named MyFET with two ports, gate and drain. Inside:

Cells vs. the top level

Everything inside define … end is a template. It isn't simulated until it's instanced at the top level (see MyFET:X1 below). The lines outside any define block are the top-level test bench.

3 · Global variables

Pin = 0
RFfreq = 2 GHz

Top-level global variables, usable anywhere a value is expected. They're also sweepable — and both are swept by the analyses below (Pin is the drive level, RFfreq the fundamental). A variable may carry a unit (2 GHz, 48 V, 10 dBm, 50 Ohm); when it does, that unit wins wherever the variable is referenced.

Any unit, not just the scaling ones

Both kinds work here: the ones that scale a number (GHz, mF, pH) and the ones that only name a quantity (V, A, W, dBm, Ohm, deg). Before circuitRF 1.0 VDS = 48 V was a parse error while RFfreq = 2 GHz was fine; the two are the same thing now.

The unit is only ever read off the end of something that is not already a valid expression, so an expression whose last token happens to spell a unit is left alone — x = 2 * f is a multiplication, not two femtoseconds.

4 · Components & instances

C:C1  Vin  n1  C=1 mF
L:L1  n2  n3  L=1 mH
...
P1Tone:P1  n1  0  Pavl=Pin dBm  Z=50 Ohm  Freq=RFfreq  Phase=0 deg  Z[0]=1 Ohm  Z[2]=30 Ohm
Vdc:V1  n2  0  Vdc=-3.05 V
Vdc:V2  VDD  0  Vdc=48 V
MyFET:X1  n3  Vout  Periphery_mm=1
IProbe:Iout  Vout  n6
...

Each line is Type:Name net1 net2 … key=value …. The first tokens after the name are the nets the component's pins connect to (order = the component's pin order); the key=value tokens are parameters. Nets named 0 are ground. Reading the key lines:

5 · Analyses (HB + parametric sweeps)

analysis HB1 type=hb Tone="RFfreq" ToneUnit=MHz MaxHarm=5 FFTOverSample=1 Tol=1e-6 DriveStepping=IfNecessary GuardHarmonic=0 Lambda=1 MaxIter=100
analysis HB1_sweep_Pin type=parametric_sweep Var=Pin Start=0 Stop=30 Step=1 Inner=HB1
analysis HB1_sweep_RFfreq type=parametric_sweep Var=RFfreq Start=1 Stop=3 Npts=3 Unit=GHz Inner=HB1_sweep_Pin

The first line is the harmonic-balance analysis; the next two wrap it in swept variables. Each analysis line is analysis Name type=… key=value …. The HB settings:

SettingMeaning
ToneThe fundamental tone — here the variable RFfreq.
ToneUnitUnit applied to the tone value when it doesn't carry its own (a unit on the variable wins).
MaxHarmHighest harmonic order kept in the spectrum (5 → DC + 5 harmonics).
FFTOverSampleTime-grid oversampling factor for the nonlinear FFT (1 = minimum).
TolNewton convergence tolerance on the harmonic-balance residual.
DriveSteppingPower/source continuation: IfNecessary ramps the drive only when a direct solve won't converge.
GuardHarmonicExtra guard harmonic(s) beyond MaxHarm for aliasing safety (0 = none).
LambdaNewton damping factor (1 = full step).
MaxIterMaximum Newton iterations per solve point.
HB settings are detailed in Simulations › Harmonic Balance.

The two parametric_sweep lines compose by Inner=: HB1_sweep_Pin runs HB1 at each Pin from 0 to 30 dBm in 1-dB steps (31 points); HB1_sweep_RFfreq runs that whole power sweep at each of 3 frequencies from 1 to 3 GHz (Npts=3, Unit=GHz). The result is a 3-frequency × 31-power harmonic-balance sweep — frequency is the outer (slow) axis, power the inner.

Sweeps wrap, they don't replace

A parametric_sweep never changes the inner analysis; it re-runs it across a variable. Nest them with Inner= to sweep more than one variable. The innermost non-sweep analysis (HB1) is what each point actually solves.

What a directive may say

type= takes one of six tokens, and each one has a fixed set of keys:

type= Verb that runs it Keys it must have
dc dc (none)
sparam sparam start, stop
hb hb Tone, or Tone[1] for a multi-tone run
loadpull lp Tone, LoadTuner, SourceTuner, Grid
loadpull_pursuit lpp Tone, LoadTuner, SourceTuner
parametric_sweep (the inner analysis's) Var, Inner, and either Values or Start+Stop

A key circuitRF does not recognise is refused, by name, with the legal ones listed — and so is a type= token it does not recognise. Every missing required key is reported at once rather than one per run. Before 1.0 an unrecognised key was accepted and thrown away in silence, so a directive could check clean and then run something quite different from what it said.

The schematic editor's own key names are accepted as aliases, since a natural way to write a directive is to copy them out of a saved .csch: an Lp/Lpp/Psa prefix and an Expr/Name/Path suffix are stripped, so LpLoadTunerName= is LoadTuner= and PsaVarName= is Var=. So are the editor's short type tags — sp, lp, lpp, sweep.

Frequency units on a sparam directive

A frequency may state its unit three ways, and they mean the same thing:

analysis SP1 type=sparam start=0.5 GHz stop=6 GHz npts=551
analysis SP1 type=sparam start=0.5 stop=6 npts=551 Unit=GHz
analysis SP1 type=sparam start="0.5" startUnit=GHz stop="6" stopUnit=GHz npts=551

Unit= sets startUnit, stopUnit and stepUnit together; any of the three given individually wins over it. A bare number with no unit anywhere is hertz.

Changed in 1.0

Unit= used to be read by parametric_sweep and silently ignored by sparam, so the second line above swept 0.5 Hz to 6 Hz. It reported no error at any stage, and the S11 it produced was flat and entirely plausible-looking — the terminations' DC reflection coefficient, nine orders of magnitude from the band that was asked for. If you have a .cnl written against the old behaviour that deliberately relied on the unit being dropped, it will now sweep the band it states.

An exported Touchstone file's declared unit follows its data, so a header saying # GHz above a first column reading 5E-10 is no longer possible. A file that was read keeps the unit its own option line declared.

Wiring, and how many nets a part takes

The nets on an instance line come before the first key=value, and the count has to match the part. A line with the wrong number is refused, naming the type, the instance and both counts:

Tuner:T1 is wired to 1 net, and a Tuner takes 2. Add the missing net before the
first 'name=value' on the line.

A part's net count is not always the pin count its schematic symbol draws — several parts have an implicit reference terminal. Port, Term and Tuner each take two nets (signal and reference) where the glyph shows one pin; SDD and Z_Port take two per port; an ideal system block such as Atten or Coupler takes two per RF port. circuitrf reference components states the net count for each part beside its pin count.

Changed in 1.0

A short instance line used to produce either Index was outside the bounds of the array — which named nothing — or, on a Tuner, no error at all: the run completed, reported ok, and gave a circuit whose bias tee delivered nothing.

Yes-and-no parameters

A parameter that is a switch — BiasTee, RefPin, RefNode, IncludeCapacitance, GroundPlane, OpVars — accepts on/true/yes/1 and off/false/no/0, in any case. Anything else is refused by name. Before 1.0 BiasTee accepted the literal on and nothing else, so BiasTee=true was read as off without a word.

6 · Measurements

measure Pin_avail_dBm = Pin
measure Pin_deliv_W = real(0.5*HB1.V("Vin",1)*conj(HB1.I("Iin",1)))
measure Pout_W = real(0.5*HB1.V("Vout",1)*conj(HB1.I("Iout",1)))
measure PDC_W = real(HB1.V("VDD",0)*HB1.I("IDC",0))
measure Eff = Pout_W/PDC_W*100
measure IRL_dB = Pin_deliv_dBm - Pin_avail_dBm; input return loss

A measure line computes a named result from an expression, evaluated at every sweep point. Expressions use the same engine as everywhere else, plus accessors into the run's data:

Results land in the run dataset and plot like any other signal. (Authored on a schematic, these come from a MEAS component — see Components › MEAS.)

7 · Labeled nets

labelednets VDD Vin Vout

Records which nets the user explicitly named (versus auto-numbered nodes like n1). The Data Display uses this to default its node picker to the meaningful nets, so you see VDD, Vin, and Vout first rather than every internal node.


See also: Simulations for the full analysis settings and algorithms · Components for each part and its parameters · New User's Guide for a gentler introduction.