Microstrip lines: quasi-TEM ports#

The tutorials so far split into two worlds: closed metal pipes (waveguides, tutorial 06/07) and ideal two-conductor lines (coax, tutorials 02–04). This one opens the third and — for most RF work — most important world: printed transmission lines. A microstrip is a flat conductor trace on a dielectric substrate over a ground plane, and its cross-section is inhomogeneous: part of the field travels in the dielectric, part in the air above. That single fact changes the character of the fundamental mode, and this tutorial is about understanding — and measuring — exactly how.

The structure is deliberately plain: a straight 50 Ω line on an FR4-class substrate inside a shielding box. Bends, junctions and components come in the next tutorial; here the line itself is the subject.


The geometry: substrate, trace, shield#

Three bricks build the cross-section: the dielectric substrate (εᵣ = 4.3, 0.8 mm — FR4 territory), the air volume above it with the trace cut out, and the PEC trace itself, 1.2 mm wide and 0.2 mm thick. Everything sits in a PEC shield box, whose floor doubles as the ground plane — the same hole-in-metal pattern as before, just with two filling materials instead of one.

Two remarks on the box. It is wide and tall enough (side walls more than four trace widths away, lid five substrate heights up) that it barely disturbs the line. And like every closed metal enclosure it has resonances of its own: box modes that would sit on top of the line’s behaviour. The band below stays under the first one — for this cross-section the lowest box mode comes in near 17 GHz, and we stop at 15.

Half the model is enough#

The cross-section is mirror-symmetric about the vertical plane through the trace centre, and so is the mode we want: the field pushes straight down from trace to ground, so the transverse field component across that plane vanishes on it, and the magnetic field threads through it at right angles. That is exactly a magnetic wall, and declaring one on the xmin face lets the mesher stop at the plane and simulate the right half only:

mio.GeometryModel(boundary_conditions={"xmin": "SymmetryPMC"})

The geometry stays as it is — full bricks, centred on the plane. The declaration alone decides how much of it gets meshed, so switching the symmetry off later means deleting one argument, not rebuilding the model. Half the cells means half the memory and roughly half the run time, and it costs nothing in accuracy — if anything the opposite, because the domain now ends exactly at the trace centre, so the discretisation is symmetric about it by construction rather than by luck.

What symmetry does cost is modes. A magnetic wall keeps only the fields that are symmetric about it, so any resonance of the box that happens to be antisymmetric is filtered out of the model entirely — convenient here, where such a mode could only be spurious clutter, but worth remembering whenever a symmetry plane is declared: the structure and the excitation must both respect it.

import matplotlib.pyplot as plt
import numpy as np

import magnelio as mio
from magnelio import geo, ports
from magnelio.constants import *

h_sub = 0.8e-3  # substrate height
w_strip = 1.2e-3  # trace width (tuned for 50 ohm, see below)
t_strip = 0.2e-3  # trace thickness
W_box = 8.0e-3  # shield width
H_box = 5.0e-3  # shield height
L = 20.0e-3  # line length
eps_r = 4.3
f_max = 15.0e9

pec = mio.Material.pec()
air = mio.Material.air()
fr4 = mio.Material.from_isotropic(name="FR4", epsilon=eps_r)

substrate = geo.Brick(origin=(-W_box / 2, 0.0, 0.0), size=(W_box, h_sub, L), material=fr4)
air_cap = geo.Brick(origin=(-W_box / 2, h_sub, 0.0), size=(W_box, H_box - h_sub, L), material=air)
strip = geo.Brick(origin=(-w_strip / 2, h_sub, 0.0), size=(w_strip, t_strip, L), material=pec)

model = mio.GeometryModel(boundary_conditions={"xmin": "SymmetryPMC"})
model.add(substrate)
model.add(geo.Difference(air_cap, strip))
model.add(strip)

model.add_port(ports.PortWaveguide(name="port1", plane="zmin", n_modes=1))
model.add_port(ports.PortWaveguide(name="port2", plane="zmax", n_modes=1))

mesh = mio.Mesh.from_geometry(
    model,
    mio.MeshControl(min_nodes_per_wavelength=25),
    f_max=f_max,
)
print(f"grid: {mesh.Nx} x {mesh.Ny} x {mesh.Nz} cells")

fig, ax = model.plot_cross_section("z", L / 2, mesh=mesh, title="microstrip cross-section")
microstrip cross-section
grid: 14 x 27 x 52 cells

The cross-section plot shows the mesher at work on thin layers: the 0.8 mm substrate and the 0.2 mm trace anchor grid planes at their material boundaries, so the y-cells grade from fine around the trace to coarse in the air above. Nobody meshed this by hand — the geometry is the meshing instruction. It also shows the symmetry plane doing its work: the drawn structure still spans the full width, but the grid covers only the right half of it.

The quasi-TEM mode#

In a coax, air everywhere, the fundamental mode is exactly TEM and its impedance is a closed formula. Here no exact TEM mode exists: the field would have to travel at two different speeds at once, in the substrate and in the air. The physical fundamental is quasi-TEM — almost transverse, zero cut-off, but with its properties set by a weighted compromise between the two dielectrics. There is no textbook formula for that compromise; the port solves the 2D cross-section problem numerically, before any time stepping:

analysis = mio.AnalysisScatteringTD(mesh=mesh, f_max=f_max, verbose=False)

report = analysis.solve_ports()["port1"]
print(report)

qtem = report.modes[0]
eps_eff_static = (C0 * qtem.gamma(10e9).imag / (2 * np.pi * 10e9)) ** 2
print(f"eps_eff (quasi-static): {eps_eff_static:.3f}")
Port 'port1' — 1 mode(s)
  cut by symmetry plane(s) xmin (PMC) — impedances are full-model values
  z_line = 51.54 Ω (numerical)
  [0] QTEM_lap00   TEM f_c = 0.0000 GHz  z_line = 51.54 Ω
eps_eff (quasi-static): 2.987

Two numbers to hold on to. The line impedance comes out at 51.5 Ω — the trace width was picked to land near 50 Ω, and it does so within 3 %. The classic Hammerstad hand formula (open microstrip, infinitely thin trace) predicts about 58 Ω for this width; the shield lid pushes the impedance down and so does the very real 0.2 mm trace thickness. Closed formulas stop where real cross-sections begin — which is precisely why the port runs a numerical mode solver.

Note what the report says above the numbers: the port window is cut by the symmetry plane, and the impedance is reported for the full model. On the meshed half the mode solver actually measures twice that value, since half a trace over half a ground plane holds half the capacitance; the two halves sit in parallel, and the port does that bookkeeping so the number on screen is the one the physical line has.

And the effective permittivity is 2.99: between air (1) and substrate (4.3), the exact weighting of the field’s split residence. The mode profile shows that split directly — the field crowds into the substrate under the trace, with a fringing skirt in the air. It is drawn across the full width: only half of it was solved, and the mirror image is filled in for the picture.

fig, ax = qtem.plot(geometry=model)
ax.set_title("quasi-TEM mode, transverse E")
quasi-TEM mode, transverse E
Text(0.5, 1.0, 'quasi-TEM mode, transverse E')

Running the line and reading the S-parameters#

A matched straight line is the simplest possible S-parameter test: everything should go through, nothing should come back.

result = analysis.run(excited=["port1"])

fig, ax = result.plot_s(("port2", "port1"), ("port1", "port1"))
ax.set_title("straight 50 Ω microstrip")

s11 = result.S("port1", "port1")
s21 = result.S("port2", "port1")
print(f"|S21|: min {20 * np.log10(np.abs(s21).min()):.2f} dB")
print(f"|S11|: max {20 * np.log10(np.abs(s11).max()):.1f} dB")
straight 50 Ω microstrip
|S21|: min -0.00 dB
|S11|: max -32.8 dB

Transmission hugs 0 dB. The reflection sits near −32 dB at its worst — and it is worth understanding what that number is. It is not a property of the line (a uniform line reflects nothing); it is the residual of the port termination absorbing a dispersive quasi-TEM wave. For exact-TEM lines, tutorial 03 showed floors beyond −100 dB, because there the termination can be made analytically exact. A quasi-TEM mode has no such exact absorber, and the −30 dB class is the honest broadband floor of its termination — background, not physics, and far below anything a real component (or a real connector) will reflect.

Dispersion: the microstrip’s signature#

The port’s ε_eff was one number — the quasi-static limit. But a microstrip is dispersive: as frequency rises the field retreats into the substrate and ε_eff creeps upward toward εᵣ. The 3D simulation contains that physics, and the phase of S21 is the instrument to extract it: over a line of length L, the mode accumulates φ = −β L, so β — and with it ε_eff = (c₀ β / ω)² — can be read off per frequency:

f_axis = result.f_axis
phase = np.unwrap(np.angle(s21))
eps_eff_td = (C0 * (-phase) / (2 * np.pi * f_axis * L)) ** 2

sel = f_axis >= 1.0e9  # phase-derived values are 0/0-noisy near DC
fig, ax = plt.subplots(figsize=(7, 4.2))
ax.plot(f_axis[sel] / 1e9, eps_eff_td[sel], label="3D simulation (from S21 phase)")
ax.axhline(eps_eff_static, color="gray", ls="--", label="port mode solver (quasi-static)")
ax.set_xlabel("frequency [GHz]")
ax.set_ylabel(r"$\varepsilon_\mathrm{eff}$")
ax.legend()
ax.set_title("microstrip dispersion")
fig.tight_layout()

for f_probe in (5e9, 10e9, 15e9):
    print(f"eps_eff({f_probe / 1e9:.0f} GHz) = {float(np.interp(f_probe, f_axis, eps_eff_td)):.3f}")
microstrip dispersion
eps_eff(5 GHz) = 3.031
eps_eff(10 GHz) = 3.140
eps_eff(15 GHz) = 3.276

The curve starts at the quasi-static value and rises to ≈ 3.3 at 15 GHz — a 10 % walk toward εᵣ across the band, right in the range classical dispersion models of the Getsinger family predict for this geometry. This is the practical reason quasi-static design formulas come with frequency disclaimers, and why a broadband design gets verified in a full-wave solver: the line the formulas describe at 1 GHz is measurably electrically longer at 15.

Where to go next#

New in this tutorial: an inhomogeneous cross-section built from two dielectrics plus a trace, a symmetry plane that halves the model for free, the quasi-TEM port with its numerically solved impedance and mode profile, the honest reading of a quasi-TEM termination floor, and dispersion extracted from the S21 phase. The next tutorial bends this line around corners and builds a real component out of it — a Wilkinson power divider, including its lumped isolation resistor.

Total running time of the script: (0 minutes 4.235 seconds)

Gallery generated by Sphinx-Gallery