comb-over

comb-over manual

comb-over analyses linear networks built from uniform coupled multiconductor TEM / quasi-TEM line sections, single lines and lumped R/L/C. It also synthesises tapped comb-line and interdigital band-pass filters. Everything runs in the browser, and the same engine runs under Node for scripting and tests.

Worked examples are in docs/examples.

Contents

  1. Running it
  2. The interface
  3. Netlist language
  4. Coupled-line sections
  5. Plots and trace expressions
  6. Tuning and optimisation
  7. Filter synthesis
  8. Export and sharing
  9. The model, its assumptions and limits
  10. Command-line tools
  11. Troubleshooting

1. Running it

The app is a static page with ES modules and has no build step. Browsers refuse to load modules from file://, so serve the folder:

python3 -m http.server 8000        # from the repository root
# open http://localhost:8000

Or enable GitHub Pages for the repository (Settings → Pages → deploy from main, root).

Tests and tools need Node 18 or later:

npm test                           # engine + synthesis tests

2. The interface

UI overview
UI overview

Area What it does
Editor (left) The netlist. It re-solves 350 ms after you stop typing, or on Run / Ctrl+Enter. Tab inserts two spaces. A parse error shows in red in the status line, and its line number is highlighted in the gutter.
Status line Port, node, section and point counts and solve time. Also shows the goal cost and worst violation per goal (if any), singular-point count and warnings.
Tune One slider per .param … tune lo hi. Sliders override the netlist values without editing the text.
• Write to netlist copies the slider values into the .param lines.
• Reset discards them.
• Editing the text also discards slider overrides.
Line sections For each coupled section: physical length, and for each mode its εeff, v/c, modal impedance and voltage eigenvector (§4.4).
Plots (right) One panel per .plot / .smith statement. Hovering shows a cursor synchronised across all plots, and the legends show the values at that frequency.
Examples… Loads a netlist from examples/.
Synthesise… Filter synthesis dialog (§7).
Optimise Runs the goal optimiser (§6). The button becomes Stop while it runs.
Open… / Save .cir Load or save the netlist text.
Data… Loads Touchstone files as .data sets, for overlay and fitting (§5.1).
RI / MA / DB + Export .sNp Touchstone export of the full S-matrix (§8).
Link Copies a URL with the netlist embedded.
Syntax Quick reference, with links into this manual.
Manual / Examples guide Open these documentation pages in a new tab.

The editor content is also kept in the browser's localStorage, so a reload restores it.


3. Netlist language

3.1 Lexical rules

3.2 Numbers and units

A number may carry an SI prefix, optionally followed by a unit, which is ignored:

Prefix f p n u (µ) m k (K) M, meg G T
× 1e-15 1e-12 1e-9 1e-6 1e-3 1e3 1e6 1e9 1e12

Examples: 2.2pF, 1.5GHz, 68nH, 25mm (= 0.025), 50Ohm.

There are three special units:

Watch out:

  • m is milli, so 1m is 0.001. A bare number in a length field is metres: len 25 is 25 m. Write 25mm.
  • M is mega, so 1M is 10⁶.

3.3 Expressions

Values anywhere in the netlist are expressions:

3.4 Directives

Directive Meaning
.freq START STOP [POINTS] [log] Sweep (required). POINTS defaults to 401. log gives log spacing.
.ref F0 Reference frequency for deg lengths. Default: the sweep centre.
.param NAME = EXPR [tune LO HI] Parameter, evaluated in file order. tune adds a slider and makes it an optimiser variable bounded by LO..HI.
.title TEXT Title, used for export file names.
.plot [ylim=LO,HI] [title=Text_with_underscores] TRACE … Rectangular plot against frequency (§5).
.smith TRACE … Smith chart of complex traces.
.goal TRACE OP VALUE [F1 F2] [w=W] Optimisation goal. OP is <, > or = (§6).
.opt maxiter=N Optimiser iteration limit (default 400).
.layout TEMPLATE SECTION… [key=value …] Board layout preview from a template (§8.1). At most one per netlist.
.substrate ID [key=value …] PCB technology (§3.6). At most one per netlist. Required by lines given by dimensions.
.data NAME FILE [overlay] [z=Z] Measured or EM-simulated S-parameters from a Touchstone file, as traces NAME.S21 (§5.1).

Unknown directives are ignored with a warning.

3.5 Nodes, ports and elements

Statement Meaning
port NODE [Z0] Port numbered in order of appearance (1, 2, …). Z0 is the real reference impedance, default 50 Ω. A port cannot be on ground, and two ports cannot share a node.
R<name> N1 N2 VALUE Resistor (non-zero).
C<name> N1 N2 VALUE [Q=..] Capacitor. With Q, it has a shunt conductance ωC/Q.
L<name> N1 N2 VALUE [Q=..] Inductor. With Q, it has a series resistance ωL/Q.
T<name> N1 N2 Z0=.. len=.. [er=..] [Q=..] [f0=..] Single uncoupled line, both ends referenced to ground.
T<name> N1 N2 W=.. len=.. [Q=..] [f0=..] Single microstrip line of width W on the .substrate (§4.6).
wire N1 N2 [N3 …] Joins nodes (a zero-ohm short). Use this instead of a 0 Ω resistor.
cap <name> N1 [N2] C=.. [type=ideal|smd|printed|trimmer] [pkg=0402|0603|0805] [Q=..] [esl=..] [d=..] [n=..] [a=..] [series=E12|E24] [mpn=..] A loading capacitor with its parasitic model (§7.5): to ground unless N2 is given.
via <name> N1 [N2] d=.. [n=..] [h=..] [Q=..] n plated vias of drill d from N1 to ground (or to N2), as the Goldfarb–Pucel inductance (§4.7). h defaults to the .substrate height.
cline NAME … end Coupled multiconductor section (§4).

Element names follow SPICE: the first letter selects the type (C1, Cload, Lchoke, T5).


3.6 Substrates

.substrate names the PCB technology the design is meant for. Pick a preset, optionally overriding values, or describe a board of your own:

.substrate jlc-ro4350b-0.508                 # preset
.substrate jlc-fr4-1.6 er=4.3 tand=0.018      # preset with overrides
.substrate custom h=0.8mm er=3.0 tand=0.002 t=18u

The presets cover the JLCPCB 2-layer boards, each with its source and access date:

The full list, with every assumption flagged, is on the generated substrate presets page.

key meaning default (custom)
h dielectric thickness required
er relative permittivity required
tand loss tangent, quoted at ftand 0
ftand frequency at which tand applies 1 GHz
t copper thickness 1 oz (34.8 µm)
sigma copper conductivity 5.8×10⁷ S/m
rough RMS copper roughness none
mintrace, minspace, mindrill fabrication limits none

Behaviour:

4. Coupled-line sections

4.1 Syntax

cline NAME [len=..] [er=..] [Q=..] [f0=..]
  <line constants>          # exactly one parameterisation, see 4.3
  er   EPS_R                # homogeneous dielectric (default 1)
  len  LENGTH               # e.g. 90deg, 25mm, tt*1deg
  f0   F                    # reference for deg (default .ref)
  Q    QU                   # unloaded Q of the line (optional)
  A    n1 n2 … nN           # terminal nodes at end A (z = 0), lines 1..N
  B    n1 n2 … nN           # terminal nodes at end B (z = ℓ)
end

end, .end and endc all close the block. N is taken from the line constants. A and B must each list exactly N nodes.

Line i at end A and line i at end B are the two ends of the same conductor. Terminations are just node names:

Sections that share node names connect, so cascades, tapped lines and stepped structures are made from several cline blocks. Example 4 shows a tapped interdigital filter built from two sections.

4.2 Length

4.3 Line constants

Use exactly one of these. Lines are numbered 1..N in the order used by A/B.

Keyword Medium Meaning
Zeo Z0e Z0o homogeneous (er) Symmetric pair, per-line even/odd impedances.
Zeo Z0e Z0o εe εo inhomogeneous Same, with even/odd effective permittivities.
Y0 [y11 y12; y21 y22] homogeneous Characteristic admittance matrix (S). Y0 = v·C.
Z0 [ … ] homogeneous Characteristic impedance matrix (Ω), Z0 = Y0⁻¹.
Cn [ … ] homogeneous Normalised Maxwell capacitance matrix C/ε (dimensionless, ε = ε0·εr). Matthaei/Getsinger convention.
Cg g1 … gN + Cm m12 m23 … homogeneous Normalised ground capacitances and adjacent mutual capacitances (all positive). Add non-adjacent terms with Cm(i,j) value.
C [ … ] and L [ … ] general Per-unit-length Maxwell capacitance (F/m) and inductance (H/m) matrices. This is the form a 2-D field solver gives; node solver/cli.mjs computes them for N strips (§10).
geom msl + W … + S … .substrate Coupled microstrip given by strip widths and gaps (§4.6). A symmetric pair (scalar W and S) uses the closed-form model; lists W [w1 w2 …], S [s12 s23 …] (or one S for all gaps) give N lines through the field solver. Also accepted inline: cline K geom=msl W=0.88mm S=0.1mm, W=[0.8mm 1mm 0.8mm].

Matrices are written [a b c; d e f; g h i], with rows separated by ; and entries by spaces or commas. They must be square and symmetric, and C must be positive definite.

The Maxwell capacitance matrix has positive diagonal entries (each line's total capacitance with all other lines grounded) and negative off-diagonal entries. Ground and mutual capacitances convert to it as

C_ii = Cg_i + Σ_j Cm_ij,    C_ij = −Cm_ij

The homogeneous forms are converted to C and L with v = c/√εr:

Form Conversion
Cn C = ε·Cn
Y0 C = Y0/v
all homogeneous forms L = C⁻¹/v²

For Zeo, the mode capacitances are Ce,o = √εe,o/(c Z0e,0o) and the mode inductances are Le,o = Z0e,0o√εe,o/c. They are combined as C = [[(Ce+Co)/2, (Ce−Co)/2], …], and likewise for L.

4.4 Modes (Line sections panel)

For each mode the panel shows:

In a homogeneous medium all modes have the same velocity, so any basis is valid. The panel then shows the eigenvectors of C (the characteristic-admittance eigenmodes).

4.5 Loss

Q on a section gives every mode an attenuation αᵢ = βᵢ/(2Q). The characteristic admittances are left real. This is a low-loss, frequency-independent-Q approximation: it gives the right insertion loss and dissipation of a high-Q resonator, but not the √f behaviour of conductor loss.

4.6 Microstrip models

js/microstrip.js gives physical models for single microstrip lines and coupled pairs. Netlists use them through lines given by dimensions on the .substrate:

.substrate jlc-ro4350b-0.508
.param w = 0.883mm tune 0.5mm 1.3mm
T1 a b W=1.07mm len=20mm                 # single microstrip line
cline K geom=msl W=w S=0.1025mm          # coupled pair (geom msl is the default when W and S are given)
  len 90deg
  A in   cpl
  B thru iso
end

How these lines are modelled:

N lines: the field solver. Give the widths and gaps as lists:

cline R geom=msl W=[0.8mm 1.2mm 1.2mm 0.8mm] S=[0.3mm 0.4mm 0.3mm]
  len 90deg
  A 0  0  0  0
  B r1 r2 r3 r4
end

C and L come from the 2-D field solver (solver/, method and validation in solver/README.md). They include the couplings between non-adjacent lines and the unequal modal velocities. model=field also sends a symmetric pair through the solver; model=closed (the default for a pair) keeps Kirschning–Jansen.

field solver closed-form pair
lines any N, unequal widths and gaps 2, symmetric
accuracy about 1e-6 for zero-thickness strips, about 5e-5 with thickness (quasi-static) about 1 % (published); the thickness correction is worse for tight gaps (below)
strip thickness rectangular strips with the substrate's copper t Jansen's effective widths
dispersion none (quasi-static) even/odd εeff
loss Wheeler's rule as a matrix (receded geometry solved again); dielectric filling factor per mode Wheeler per mode; filling factor
cost per new geometry 3 solves (with dielectric, in air, receded): about 50 ms for a thick pair, 0.1 s for 5 lines, 0.6 s for 10 microseconds

With zero-thickness copper (.substrate … t=0), the two agree for a symmetric pair to within Kirschning–Jansen's accuracy: for W 1.1 mm and S 0.2 mm on 0.508 mm RO4350B, Ze differs by 0.3 %, εe by 0.2 %, Zo by 0.1 % and εo by less than 0.01 % (the closed form includes dispersion at 2 GHz).

Strip thickness. The solver models each strip as a rectangle of the substrate's copper thickness, so the side walls and the air between them are included. Thickness matters on thin substrates: on 0.508 mm RO4350B, 35 µm copper lowers Zo by 5–8 % for gaps of 0.1–0.2 mm.

For single lines, the solver's thickness effect agrees with Hammerstad–Jensen's correction to 0.1 % of Z0. For tight coupled pairs, the closed-form correction (Jansen's effective widths, published for S ≫ 2t) captures only part of it, because the air-filled gap between the walls pulls the odd mode's εeff down:

35 µm copper, RO4350B field solver vs closed form, both with t
h 0.508, W 1.1, S 0.2 mm Ze +0.7 %, Zo −1.6 %, εe −0.9 %, εo −4.9 %
h 0.508, W 1.1, S 0.1 mm Ze +0.4 %, Zo −3.1 %, εe −0.9 %, εo −7.2 %
h 0.254, W 0.5, S 0.15 mm Ze +1.5 %, Zo −1.7 %, εe −1.9 %, εo −7.2 %
h 1.524, W 3, S 0.3 mm Ze 0.0 %, Zo −1.3 %, εe −0.3 %, εo −3.0 %

So for tight gaps on thin boards use model=field for pairs too. The independent checks in solver/README.md back the solver's numbers. Not modelled: etch profile (trapezoidal strips), solder mask, plating.

Loss with the field solver. Each mode's Q comes from two terms:

For a symmetric pair, the even and odd Q agree with the closed-form path to 0.1 %.

You can also use the models directly, through the command-line calculator or from JavaScript:

node tools/mscalc.mjs --sub jlc-ro4350b-0.508 --z0 50 --f 2G          # width for 50 Ω
node tools/mscalc.mjs --sub jlc-ro4350b-0.508 --ze 69.4 --zo 36 --f 5G  # W and S for a 10 dB coupler
node tools/mscalc.mjs --sub jlc-fr4-1.6 --w 2.8mm --f 1G                 # analyse a geometry
model notes
single line Hammerstad & Jensen (1980), with their strip-thickness correction quasi-static Z0, εeff
single-line dispersion Kirschning & Jansen (1982) for εeff; Jansen & Kirschning (1983) for Z0 uses the thickness-corrected width, as scikit-rf does
coupled pair Kirschning & Jansen (1984) quasi-static even/odd Z and εeff; dispersion of even/odd εeff
coupled strip thickness Jansen's even/odd effective widths with the Hammerstad–Bekkadal ΔW published for S ≫ 2t
conductor loss Wheeler's incremental-inductance rule applied to the same models, with the Hammerstad roughness factor default; model: 'hammerstad' gives Rs/(Z0·W)·Ki·Kr instead
dielectric loss αd = π f/c · εr(εeff − 1)/(√εeff (εr − 1)) · tan δ

Validity ranges. The functions return warnings when an input is outside the published range:

model range
single line 0.01 ≤ W/h ≤ 100
single-line dispersion f·h ≤ 60 GHz·mm, εr ≤ 20
coupled pair 0.1 ≤ W/h ≤ 10, 0.1 ≤ S/h ≤ 10, εr ≤ 18
coupled dispersion f·h ≤ 25 GHz·mm

They also warn about gaps below 20 t and about copper thinner than 3 skin depths.

Synthesis.

Some results on JLC's 0.508 mm RO4350B:

Validation (tests/microstrip.mjs):

Known limitations.


4.7 Discontinuities

The line model is pure TEM, so it has no parasitics of its own (§9.3). The layout templates add the most important ones automatically, and a via element is available for netlists written by hand. The models live in js/discontinuities.js.

discontinuity model where it is used
Open end Equivalent length extension Δl of the open-circuited line (Kirschning, Jansen & Koster, Electronics Letters 1981; stated accuracy about 2.5 % for 0.01 ≤ W/h ≤ 100, εr ≤ 128). Marchand main line: a Tmain_oe … len=Δl stub on the open end in the as-built and core netlists.
Via to ground L = (μ0/2π)[h ln((h + √(r² + h²))/r) + 1.5(r − √(r² + h²))], r = d/2 (Goldfarb & Pucel, IEEE MGWL 1991). n vias in parallel give L/n, with mutual coupling neglected, which is optimistic for closely spaced vias. via element; Marchand grounded ends (a stub to the via-pad centre plus via … n=2).
Edge-connector launch Placeholder series L / shunt C at each port, with parameters sma_l and sma_c. Every connector port (§8.4).
T-junction Not yet. Needed only by tapped comb-line and interdigital layouts (issue #15).

Typical values:

0.508 mm RO4350B 1.524 mm RO4350B
Open end Δl (50 Ω line) 0.22 mm (Δl/h 0.42) 0.65 mm (Δl/h 0.43)
One 0.3 mm via 83 pH 0.50 nH

Effect on the Marchand demo. On 1.524 mm RO4350B, the 2 × 0.3 mm vias at each grounded end (0.25 nH, j1.6 Ω at 1 GHz) and the 0.52 mm open-end extension move the outputs by less than 0.1 dB at f0.

Checks. The KJK Δl agrees with Hammerstad's older formula within 5 % for 0.5 ≤ W/h ≤ 3, and differs by up to 25 % at the extremes of W/h. The via formula is checked against its own long-via limit and against S11 = (jωL − 50)/(jωL + 50) for a via at a port (tests/discontinuities.mjs). Neither model has been checked here against EM simulation or measurement; issues #16 and #17 cover that.

5. Plots and trace expressions

A trace is an expression evaluated at every sweep point. It is complex unless it passes through a real function.

Variables

Name Meaning
S21, S11, … S-parameters with single-digit port numbers. Sᵢⱼ is the wave out of port i for a wave into port j.
S_10_3, S(i,j) Any port numbers.
NAME.S21, NAME.S_10_3 S-parameters of a .data set (§5.1).
f or freq Frequency in Hz.
j Imaginary unit.
constants and .param names As in §3.3.

Operators: + − * /, and ^ with a real exponent.

Function Result
db(x) 20 log₁₀|x|
db10(x) 10 log₁₀|x|
mag(x), abs(x) |x|
ang(x) phase in degrees, −180…180
angu(x) unwrapped phase in degrees
gd(x) group delay −dφ/dω in seconds, by central differences
re(x), im(x) real and imaginary parts
conj(x), sqrt(x) complex conjugate, principal square root
vswr(x) (1+|x|)/(1−|x|)
cheby(n,ripple_dB,f0,fbw) ideal Chebyshev band-pass |S21| with the mapping Ω = (f/f0 − f0/f)/fbw
butter(n,f0,fbw) ideal Butterworth |S21| (fbw is the 3 dB bandwidth)

Useful combinations:

A rectangular plot shows the real part of each trace. A Smith chart plots the complex value; the small square marks the first sweep point.

5.1 Measured data

.data meas board.s4p overlay
.data em   sonnet.s2p z=50
.plot db(S21) db(meas.S21)
.goal db(S31)-db(meas.S31) = 0 1G 2G

.data NAME FILE reads a Touchstone file (.sNp, version 1.1 or 2.0; S-parameters only) and makes its S-matrix available as NAME.Sij in plots, Smith charts and goals. In the browser, Data… loads one or more files and adds a .data measK FILE overlay line for each file that is not already named. Loaded files are kept in the browser's local storage, matched by file name. The command-line tools read FILE relative to the netlist.

Option Effect
overlay Every trace of S-parameters in a .plot or .smith gets a dotted twin from the data, in the same colour: db(S21) is drawn again as db(NAME.S21).
z=Z Renormalises the data to Z (one value for all ports) through Z = √z (I+S)(I−S)⁻¹ √z.

The data are interpolated onto the sweep: magnitude and unwrapped phase, each linear in frequency. Sweep points outside the file's frequency range are blank, and the status line says how many there are. The status also warns when the file is not loaded, when its port count differs from the netlist's, or when its reference impedances differ from the netlist's ports (add z= to renormalise). Noise data in 2-port files is ignored.

A .goal against data fits the model to a measurement. For example, with .param values marked tune for a launch's series L and shunt C, .goal mag(S11-meas.S11) < 0.001 recovers them from a measured S11.

A Touchstone file exported by comb-over reads back to the same S-matrix (to the 10 significant digits written), so an export can be used as a reference for later changes.


6. Tuning and optimisation

Tuning. .param x = 2.3p tune 1p 5p creates a slider from 1 to 5 pF. The slider is logarithmic when LO > 0 and HI/LO > 10, linear otherwise. Dragging it re-solves immediately.

Goals. .goal TRACE OP VALUE [F1 F2] [w=W], for example:

.goal db(S11) < -20 0.95G 1.05G
.goal db(S21) < -40 0.5G 0.8G w=0.5
.goal ang(S21/S43) = 90 1.3G 2.7G

A compact form .goal db(S11)<-20 0.95G 1.05G is also accepted. The trace should be real-valued; the real part is used.

Cost. For each sweep point inside [F1, F2], the violation v is:

OP v
< max(0, y − VALUE)
> max(0, VALUE − y)
= y − VALUE

Each goal contributes w·(mean(v²) + max(v)²). The max term pushes towards an equiripple (minimax) result.

Optimiser. Nelder–Mead over all tune parameters. Each parameter is mapped to [0, 1] within its LO..HI range, logarithmically under the same rule as the sliders, and leaving the box is penalised.

Optimisation is local. Start from a sensible design (§7) and give realistic ranges.


7. Filter synthesis

Synthesise… generates a complete netlist for a tapped comb-line or interdigital band-pass filter.

Synthesis dialog
Synthesis dialog

Field Meaning
Structure Comb-line: all lines shorted at one end, with a loading capacitor at the other end and length θ0 < 90°.
Interdigital: lines shorted at alternate ends, θ0 = 90°, no capacitors.
Response, order n, ripple Chebyshev or Butterworth prototype. The return loss equivalent to the ripple is shown.
f0, bandwidth Centre and fractional bandwidth: ripple bandwidth for Chebyshev, 3 dB bandwidth for Butterworth. The mapping is Ω = (f/f0 − f0/f)/FBW.
θ0 Comb-line resonator line length at f0.
Zr Resonator line impedance with all neighbouring lines grounded, i.e. 1/(v·Cii). This sets the admittance level; the couplings set the ratios.
εr, R0 Dielectric (homogeneous) and port impedance.
Refine numerically Corrections using the full model (below).
Polish Runs the optimiser on the result.

7.1 Closed form

Prototype values g₀…gn+1 give

k(i,i+1) = FBW / √(g_i g_{i+1})        Qe = g0 g1 / FBW

Take each resonator's open end as a node, with all other resonator nodes grounded. Let Yₐ = 1/Zr and Yₘ be the adjacent mutual admittance (v × mutual capacitance).

Comb-line Interdigital
Node susceptance Bii = ωCl − Yₐ cot θ Bii = −Yₐ cot θ
Coupling susceptance Bi,i+1 = Yₘ cot θ |Bi,i+1| = Yₘ csc θ
Resonance Cl = Yₐ cot θ0 / ω0 θ0 = 90°
Slope parameter b = (ω0/2) dB/dω (Yₐ/2)(cot θ0 + θ0 csc² θ0) πYₐ/4
Inverter J Yₘ cot θ0 Yₘ

Setting J = k·b gives each Yₘ. The normalised capacitances are then Cg = (Yₐ − ΣYₘ)·η and Cm = Yₘ·η, with η = η0/√εr.

For the tap, the resonator voltage is ∝ sin βz from the short. A load R0 tapped at θt therefore appears at the open end as G0·(sin θt/sin θ0)², so

sin θt = sin θ0 · √(b R0 / Qe)

7.2 Refinement

  1. Couplings. For each pair, the split-mode frequencies (Bii ± Bi,i+1 = 0) are found by bisection. Yₘ is then scaled until (fo² − fe²)/(fo² + fe²) equals the target k. Within the model this is exact.
  2. Tap. The first resonator is modelled with the full multiconductor sections, singly loaded, with the others grounded. Qe = ωr·τmax/4 comes from the S11 group-delay peak.
    • θt is iterated until Qe is on target.
    • The end capacitor is Newton-corrected (ΔC = (ωr − ω0)·2b/ω0²) until the resonance is at f0.
    • Interdigital end resonators get a small trim capacitor for this, which can only lower their frequency.
  3. Centre and bandwidth. The realised centre (geometric mean of the −3 dB points) and −3 dB bandwidth are measured. The design f0 and FBW are pre-distorted until both match the ideal prototype.

7.3 The generated netlist

The generated netlist contains:

7.4 What to expect

These figures are from npm test and Example 3:


7.5 Loading capacitors

A comb-line's resonators are loaded by capacitors at their open ends. Loading capacitors in the synthesis dialog chooses how they are built. The comb sections stay as synthesised, with their εr; the capacitor substrate sets only the pads and ground vias.

option model (cap … type=) the synthesiser
ideal C as before: cl1, cl tunable
SMD 0402 / 0603 / 0805 hot pad shunt C → ESL → C (Q) → cold pad → ground vias Finds the nominal value whose effective capacitance at f0 equals the ideal one, snaps it to E24 or E12, re-tunes the common line length (and tap) to absorb the snapping, and reports each resonator's residual detuning. th0 becomes tunable for polishing.
trimmer (Murata TZC3) pad C → ESL → C (Q) → ground vias Picks the narrowest TZC3 that covers each value with ±10 % adjustment. The cl1/cl sliders span the trimmer's range, so they are the trimmer screws.
printed pad square pad to the bottom ground: an open microstrip stub, side a + open-end Δl Sizes each pad for its value at f0 and warns when the substrate cannot reach it

The models (js/capacitors.js):

Sources and assumptions:

Measured on a 5-pole, 6 % comb-line at 1 GHz (θ0 40°, Zr 70 Ω, lossless lines), against the ideal design, which is centred at 1.0006 GHz with a 7.08 % −3 dB bandwidth:

option capacitors centre −3 dB BW (below the peak) peak S21 note
SMD 0603, E24, 0.508 mm RO4350B 2.4 pF × 5 −0.21 % 6.43 % −1.37 dB length 39.92° → 40.18°; residual detuning ±0.3 % per resonator
SMD 0402, E12 2.7 pF × 5 −0.18 % 6.73 % −1.38 dB length 39.92° → 38.00°
printed, 0.508 mm RO4350B 5.9 mm square pads −0.03 % 6.66 % −1.54 dB pad conductor and dielectric loss
trimmer, 1.524 mm RO4350B TZC3Z030 (1.4–3 pF) −0.06 % (0.00 % lossless) 5.64 % −4.21 dB with the assumed Q 100

The SMD loss is set by the assumed Q of 300. The narrower bandwidth comes from the ESL, which makes the effective capacitance rise with frequency and steepens each resonator's slope. Polish recovers the bandwidth by retuning cg/cm/tt and the now-tunable th0.

The designer warns, in the netlist comments and the status line, when an option cannot reach the value:

Footprints. capFootprint() in js/kicad.js gives each option's KiCad footprint: pad 1 on the resonator end, pad 2 and its via land to ground. The comb-line layouts of issue #15 place them. node tools/kicad.mjs --caps SUBSTRATE --out DIR writes them as a library, and CI checks that library in KiCad.

8. Export and sharing

Touchstone. Exports the full S-matrix at all sweep points, with frequency in Hz, as RI (real/imaginary), MA (magnitude/angle°) or DB (dB/angle°).

Port reference impedances Format
All equal Touchstone 1.1 (# Hz S RI R 50)
Different Touchstone 2.0 with a [Reference] line (2-ports declare [Two-Port Data Order] 21_12)

Link. Encodes the netlist in the URL fragment (#n=…). Nothing is uploaded.

Example links. index.html?example=NAME.cir opens a file from examples/; the worked-example pages use this for their "open in designer" links.

When a netlist arrives from a link of either kind and you already had a different one in the editor, your previous netlist is kept and a Restore it button appears above the status line. The query string is removed after loading, so reloading the page keeps your edits.

Save .cir / Open…. Plain text netlists.

8.1 Layout

A .layout statement turns sections given by physical dimensions (§4.6) into a board layout. The designer shows it in the layout panel above the plots. Worked example 8 goes from an impedance-defined coupler to a KiCad board using only the panel.

The layout panel. The netlist text stays the source of truth: every control rewrites a line of it and re-runs.

Hovering a copper shape in the preview shows its net and width.

Layout preview
Layout preview

.layout coupler K                         # 4-port coupled-line coupler
.layout schiffman CS TREF                 # C-section (far ends joined) + reference line
.layout marchand S1 S2                    # two coupled sections, grounded secondaries
option default meaning
connector amphenol-132289 edge-launch connector at every port (§8.4); none for bare feeds to the edge
launch matched matched: the connector's signal pad is the feed width; library: KiCad's 1.5 mm pad plus a taper (§8.4)
pitch 12 mm (10 mm without a connector) spacing between neighbouring ports on the same edge; the default clears the connector's courtyard (11.16 mm)
feed connector pad + launch taper + 1 mm, rounded up to 0.5 mm (5 mm without a connector) length of the straight port-impedance feed at each port
margin 6 mm (4 mm without a connector) board margin beyond the copper; the default keeps the connector's ground pads on the board
gap (marchand) 0.5 mm or the fab minimum separation between the two sections' secondary lines, where the outputs come out
drill (marchand) 0.3 mm or the fab minimum grounding-via drill; the pad is 0.3 mm larger
vias (marchand) 2 grounding vias at each grounded end

What the templates do:

Checks. The layout is checked against the substrate's fabrication limits:

Results appear under the preview.

As-built netlist. The As-built netlist button opens the netlist of what is laid out: the original sections plus every taper, feed line and connector pad section. Simulate it to see what the feeds do to the response; your previous netlist is kept for Restore it.

For the 10 dB coupler at 5 GHz the as-built coupling is −10.25 dB, against −10.07 dB for the bare section, and the match stays below −34 dB. The as-built netlist does not model:

It does model the discontinuities of §4.7: grounding vias (Marchand), the open end of the Marchand main line, and a tunable placeholder for each connector launch.

SVG downloads the preview. From the command line:

node tools/layout.mjs examples/coupler-10dB-ro4350b.cir --svg coupler.svg --netlist asbuilt.cir

8.2 KiCad component library

KiCad library (on the layout card) downloads the core of the layout as a self-contained KiCad 9 library, a footprint plus a matching schematic symbol, so the part can be reused in any design. The board around it, with its connectors, is §8.3.

The zip holds one folder:

file contents
LIB.pretty/PART.kicad_mod footprint (board file version 20241229, as written by KiCad 9.0)
LIB.kicad_sym symbol, one pin per port (plus GND for the Marchand); its Footprint field is LIB:PART
fp-lib-table, sym-lib-table project library tables using ${KIPRJMOD}: put them next to your .kicad_pro, or add the library under Preferences → Manage Libraries
PART.cir the core netlist: the part alone, with its ports at the reference planes
README.txt the pin list, substrate and notes

The part name carries the template, f0 and substrate, e.g. COUPLER_5G00_jlc-ro4350b-0.508, because the copper is only right on that stack-up. The library name is the same as the part name.

What is in the footprint:

Core netlist. PART.cir is the as-built netlist without the board feeds, so it can be dropped into another simulation. For the 10 dB coupler at 5 GHz: bare section −10.07 dB, core −10.14 dB, as built with feeds and connector pads −10.25 dB.

The Schiffman reference line in the part is the designed length plus the C-section's in-part escape lengths, and its two board feeds are as long as the C-section's feeds. The phase difference is therefore set by the part alone.

From the command line:

node tools/kicad.mjs examples/coupler-10dB-ro4350b.cir --out lib/ [--zip] [--mask covered] [--keepout 2] [--name PART]

--board writes the whole KiCad project instead (§8.3).

8.3 KiCad board

KiCad project (on the layout card) downloads a complete KiCad 9 project for the layout: open the .kicad_pro, press B to fill the zones, and the board is ready for review and Gerber export.

file contents
NAME.kicad_pro, NAME.kicad_pcb the project and board (file version 20241229)
NAME-asbuilt.cir the as-built netlist of the whole board (§8.1)
NAME.pretty, NAME.kicad_sym, lib tables, NAME.cir the component library of §8.2, which also holds the connector footprints
NAME.kicad_dru a custom design rule that lets the connector pads meet the board edge

What is on the board:

The F.Cu pour can be switched off from the command line (--no-top-pour). On thick substrates the 3h clearance leaves little room for it anyway. The 1.524 mm Marchand and Schiffman demos get only a few vias, while the 0.508 mm coupler gets a full fence.

Validation. Every pull request generates KiCad projects and checks them with KiCad's own tools in the official kicad/kicad containers (tools/kicad-check.sh). The projects are the three demos, worked example 8 (converted to microstrip as the layout panel does it) and a deliberately broken board. The checks are:

DRC is judged against tools/kicad-expected.json: any violation type not listed for a design fails the build, warnings included. Allowed today: "isolated copper" on the 1.524 mm Marchand and the example 8 board, where the 3h keepout leaves only small top-pour islands.

Two deliberate faults check that faults are caught:

KiCad 9.0.8 is the target and gates merges. KiCad 10.0.7 runs the same checks but only reports, so format changes show up early. Under KiCad 10 the footprint re-save comparison is informational, because KiCad 10 converts files to its own format on save.

The Marchand board has two "isolated copper" warnings. On 1.524 mm material the 3h keepout around the part, a rectangle, leaves only small top-pour islands, each touching a single connector ground pad. A keepout that follows the conductors instead of their bounding box would fix this.

node tools/kicad.mjs examples/coupler-10dB-ro4350b.cir --out board/ --board [--zip] [--no-top-pour] [--via-pitch MM]

8.4 Edge-launch connectors

Every port gets an edge-launch SMA. The default, and so far the only one, is the Amphenol RF 132289, a 50 Ω jack rated 0–18 GHz for 0.062" (1.57 mm) boards (listing). Its pads come from KiCad's library footprint Connector_Coaxial:SMA_Amphenol_132289_EdgeMount (CC-BY-SA 4.0 with the KiCad library exception):

The generated footprint (SMA_Amphenol_132289_EdgeMount_W<feed width>) has its origin on the board edge. It adds three thru-hole ground vias in each ground pad to tie the top and bottom ground together at the launch. The pads carry paste, so expect solder to wick into those vias. A .kicad_dru rule (severity ignore) lets the connector's pads meet the board edge.

Launch. The signal pad is a 5 mm length of line, so its width matters:

launch= signal pad when
matched (default) the feed width wf the pad is simply part of the 50 Ω line
library KiCad's 1.5 mm, then a taper of max(1 mm, 2|Δw|) to wf to keep the stock land pattern

On 0.508 mm RO4350B the stock 1.5 mm pad is a 41 Ω, 5 mm section, about 50° at 5 GHz, at every port. It moves the 10 dB coupler's coupling from −10.07 dB to −11.29 dB. With launch=matched it stays at −10.25 dB.

The as-built netlist models the pad and any taper as line sections. The transition into the connector is a placeholder (§4.7): a series L (sma_l) and shunt C (sma_c) at every port. They are effectively zero by default, with tune ranges of 1 pH–2 nH and 1 fF–1 pF, so they can be fitted to EM or measured data.

Board thickness. The connector's ground legs straddle the board. The layout card warns when the board (core + 2 × copper) is more than ±0.15 mm from the connector's 1.57 mm, which is the case for the 0.508 mm coupler demo. Either choose a variant of the connector made for the actual thickness, or use a 1.57 mm stack-up. The 1.524 mm RO4350B demos (1.594 mm with copper) fit.


9. The model, its assumptions and limits

9.1 Section admittance matrix

For per-unit-length Maxwell C and inductance L:

Step Definition
Symmetrised modal problem M = C½ L C½ = P Λ Pᵀ
Modal velocities vᵢ = 1/√λᵢ
Voltage eigenvectors Tv = C−½ P
Weight matrix W = C½ P

With terminals ordered A₁…AN, B₁…BN and currents flowing into the section:

Y11 = Y22 =  W · diag(vᵢ coth γᵢℓ) · Wᵀ
Y12 = Y21 = −W · diag(vᵢ csch γᵢℓ) · Wᵀ          γᵢ = βᵢ (1/(2Q) + j)

In a homogeneous medium this reduces to Y11 = vC coth γℓ.

9.2 Network solution

All sections and lumped elements are stamped into one nodal admittance matrix Y (n×n, ground eliminated). Each port node is then terminated in its reference conductance G, and

(Y + G_ports) X = E_ports,        S = 2 √G · X_pp · √G − I

This is valid for real reference impedances.

The terminated form stays well conditioned where a Kron reduction onto the ports would not. An example is a resonance of the network with its ports shorted: the Marchand balun has one at f0/2.

At exact half-wave frequencies a lossless line's admittance is singular. Such points are evaluated 1 ppb away from the requested frequency, which gives the limiting value. Points that still fail are shown as gaps and counted in the status line.

9.3 Not modelled

9.4 Verification


10. Command-line tools

Command Purpose
node tools/summary.mjs FILE.cir 11 21 … Text table of dB/angle for the given S-parameters, plus section lengths and εeff.
node tools/opt.mjs FILE.cir [iters] [rounds] Runs the optimiser headless and writes tuned values back into the file. .data files are read relative to the netlist.
tools/measure.mjs Helper module (load, at, band, extreme) used to compute the numbers quoted in the worked examples.
node solver/cli.mjs --W w1,w2,… --S s12,… (--h H | --stripline B) --er ER [--t T] [--cover HC] [--json] 2-D field solver: C, L and modal εeff of N strips (mm; --t thickness, default 0) in microstrip, covered microstrip or stripline, and the C [...] L [...] lines for a cline. Method and validation: solver/README.md.
node tools/mscalc.mjs --sub ID (--z0 Z | --ze Ze --zo Zo | --w W [--s S]) [--f F] Microstrip calculator: width for Z0, W/S for even/odd impedances, or analysis of a geometry, including losses (§4.6). --list shows the substrate presets.
node tools/layout.mjs FILE.cir [--svg F] [--netlist F] [--json F] Builds the .layout of a netlist, reports the board size and design-rule results, and writes the SVG preview, the as-built netlist or the layout data (§8.1). Exits with code 2 if the layout breaks a design rule.
node tools/kicad.mjs FILE.cir --out DIR [--board] [--zip] [--mask covered] [--keepout MM] [--name PART] [--no-top-pour] [--via-pitch MM] Writes the .layout core as a self-contained KiCad 9 library (§8.2), or with --board the whole KiCad project (§8.3).
npm run docs Rebuilds these HTML documentation pages from the Markdown in docs/ (npm test fails if they are out of date).
python3 tools/docfigs.py Regenerates the figures in docs/img (needs Playwright with Chromium and a local server on port 8765).

The engine modules have no DOM dependencies and can be imported directly:

import { parseNetlist } from './js/netlist.js';
import { solve } from './js/solver.js';
import { design } from './js/synth.js';
const r = solve(parseNetlist(text));      // r.S(2,1) → {re, im} arrays over r.freq

11. Troubleshooting

Symptom Cause / fix
line N: unexpected … An expression contains a space. Remove it, or wrap it in brackets.
A filter appears at a silly frequency or with a silly length A unit problem: len 25 is 25 m, and 1m is 1 milli. Check the Line sections panel for the physical length.
capacitance matrix is not positive definite The mutual capacitances are larger than the self capacitances. Check the signs: Maxwell off-diagonals are negative; Cg/Cm are all positive.
two ports share a node Put a wire between two nodes and a port on each, or use one port.
Gaps in a plot, status says "singular" An exactly singular lossless point that survived the retry. Shift the sweep slightly or add a little loss (Q).
Optimiser stalls with nonzero cost The goals conflict, or the range bounds are active (a slider pinned at an end). Widen the ranges, re-weight the goals, or run it again from the new point.
Synthesised filter return loss a few dB short Expected (§7.4). Tick Polish, or press Optimise.