Skip to content

Intro to Transmission Line Modeling

Download as:

The stripline is a commonly used transmission line in printed circuit board (PCB) designs. Compared to the microstrip, the advantage of the stripline includes better shielding from EM interference, lower radiation loss, and better performance at higher frequencies. These strengths make it suitable for use in high-speed interconnects, where bandwidth, package size, and signal integrity are key concerns.

In this notebook, we will build a 100 Ohm edge-coupled differential stripline and simulate it from 1 to 70 GHz. We will demonstrate the typical transmission line analysis workflow in Flex RF, including how to use the TerminalWavePort feature, how to perform 2D mode calculations, and how to run a full-wave 3D analysis.

import matplotlib.pyplot as plt
import numpy as np
import flex_rf.tidy3d as rf
import flex_rf.web as web
rf.config.logging.level = 'ERROR'

We begin by defining some key parameters. The design impedance Z0Z_0 for the differential mode will be 100 Ohms. The geometry parameters are chosen with that in mind. Material properties are assumed constant over the frequency band.

# Frequency range (Hz)
f_min, f_max = (1e9, 70e9)
f0 = (f_max + f_min) / 2
freqs = np.linspace(f_min, f_max, 101)
# Geometry
mil = 25.4 # conversion factor: mils to microns (default unit)
w = 3.2 * mil # Signal strip width
t = 0.7 * mil # Conductor thickness
h = 10.7 * mil # Substrate thickness
se = 7 * mil # Gap between edge-coupled pair
L = 1000 * mil # Line length
W_port = 50 * mil # Wave port width
L_sub, W_sub = (L + 10 * mil, W_port + 30 * mil) # Substrate length and width
# Material properties
cond = 60 # Metal conductivity in S/um
eps = 4.4 # Relative permittivity, substrate
losstan = 0.0012 # Loss tangent, substrate

We define the simulation mediums below. Loss is assumed constant over the frequency band.

  • Metal: The LossyMetalMedium feature implements the surface impedance boundary condition for lossy metals .
  • Dielectric: The constant_loss_tangent_model() method automatically generates a PoleResidue medium given permittivity, loss tangent, and frequency range parameters.
med_sub = rf.FastDispersionFitter.constant_loss_tangent_model(eps, losstan, (f_min, f_max), tolerance_rms=2e-4)
med_metal = rf.LossyMetalMedium(conductivity=cond, frequency_range=(f_min, f_max), name="Metal")
Best weighted RMS error: 0.00018 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100% 0:00:00

Use the VisualizationSpec feature to (optionally) control the fill color of the materials during plotting.

med_sub = med_sub.updated_copy(
viz_spec=rf.VisualizationSpec(facecolor="#7cc48d")
) # green tone for substrate
med_metal = med_metal.updated_copy(
viz_spec=rf.VisualizationSpec(facecolor="#ce7e00")
) # brown tone for metal

The structures are created below.

# Substrate
str_sub = rf.Structure(geometry=rf.Box(center=(0, 0, 0), size=(W_sub, h, L_sub)), medium=med_sub)
# Signal strips
str_strip_left = rf.Structure(
geometry=rf.Box(center=(-(se + w) / 2, 0, 0), size=(w, t, L_sub)), medium=med_metal
)
str_strip_right = rf.Structure(
geometry=rf.Box(center=((se + w) / 2, 0, 0), size=(w, t, L_sub)), medium=med_metal
)
# Top and bottom ground planes
str_gnd_top = rf.Structure(
geometry=rf.Box(center=(0, h / 2 + t, 0), size=(W_sub, 2*t, L_sub)), medium=med_metal
)
str_gnd_bot = rf.Structure(
geometry=rf.Box(center=(0, -h / 2 - t, 0), size=(W_sub, 2*t, L_sub)), medium=med_metal
)

The LayerRefinementSpec feature controls grid refinement around metallic structures. We apply extra refinement around the signal traces, and basic refinement for the top and bottom ground planes.

# Create a LayerRefinementSpec from signal trace structures
lr1 = rf.LayerRefinementSpec.from_structures(
structures=[str_strip_left, str_strip_right],
min_steps_along_axis=5, # Min grid cells along normal direction
corner_refinement=rf.GridRefinement(
dl=t / 5, num_cells=2
), # snap to corners and apply added refinement
)
# Layer refinement for top and bottom ground planes
lr2 = rf.LayerRefinementSpec(
center=(0, h / 2 + t / 2, 0), size=(rf.inf, t, rf.inf), axis=1, min_steps_along_axis=2
)
lr3 = lr2.updated_copy(center=(0, -h / 2 - t / 2, 0))

The rest of the grid is automatically generated based on the wavelength.

# Define overall grid specification
grid_spec = rf.GridSpec.auto(
wavelength=rf.C_0 / f_max,
min_steps_per_wvl=18,
layer_refinement_specs=[lr1, lr2, lr3],
)

The top (+y) and bottom (-y) boundaries of this model are adjacent to the metal ground planes and can be set to PEC. All other boundaries are open and thus truncated by perfectly matched layers (PMLs).

boundary_spec = rf.BoundarySpec(
x=rf.Boundary.pml(),
y=rf.Boundary.pec(),
z=rf.Boundary.pml(),
)

For visualization purposes, we add a FieldMonitor between the signal plane and the bottom ground plane.

mon_1 = rf.FieldMonitor(
center=(0, -h / 4, 0),
size=(rf.inf, 0, rf.inf),
freqs=[f_max],
name="monitor 1",
)

The TerminalWavePort feature is used to excite TEM modes in terminal-based structures. It is most suitable for single- and multi-ended transmission lines in PCBs and related applications.

Within each port plane, the port boundary should enclose all signal traces and touch/contact any adjacent ground planes for best results. All enclosed traces are automatically detected and assigned name labels T0, T1, ... in left-to-right, bottom-to-top order.

For differential/common mode excitations, use the optional differential_pairs parameter to group each double-ended pair of terminals. Below, we assign the grouping ('T0', 'T1') to indicate that the two signal traces are to be treated as a double-ended line.

WP1 = rf.TerminalWavePort(
center=(0, 0, -L / 2),
size=(W_port, h, 0),
direction="+",
name="WP1",
differential_pairs=[("T0", "T1")],
)
WP2 = WP1.updated_copy(
center=(0, 0, L / 2),
direction="-",
name="WP2",
)

Define the Simulation and TerminalComponentModeler

Section titled “Define the Simulation and TerminalComponentModeler”

The Simulation object in Tidy3D contains information about the entire simulation environment, including boundary conditions, grid, structures, and monitors.

sim = rf.Simulation(
size=(W_sub, h + 2 * t, L_sub),
center=(0, 0, 0),
grid_spec=grid_spec,
boundary_spec=boundary_spec,
structures=[str_sub, str_strip_left, str_strip_right, str_gnd_top, str_gnd_bot],
monitors=[mon_1],
run_time=1e-9,
plot_length_units="mil",
)

The TerminalComponentModeler conducts a port sweep based on our previously defined Simulation in order to construct the full S-parameter matrix.

tcm = rf.TerminalComponentModeler(
simulation=sim,
ports=[WP1, WP2], # terminal wave ports
freqs=freqs, # S-parameter frequency points
)

Below, we plot the wave port plane of WP1 and the simulation grid (gray lines). The two terminals T0 and T1 are labeled, as well as the double-ended pair Diff0.

# Inspect port and transverse grid
fig, ax = plt.subplots(figsize=(10, 6), tight_layout=True)
sim.plot_grid(z=1, ax=ax)
tcm.plot_port("WP1", ax=ax)
plt.show()

In this section, we demo a 2D ModeSolver calculation. This calculation provides the key transmission line parameters, such as attenuation α\alpha, characteristic impedance Z0Z_0, and effective index neffn_{\text{eff}}.

The ModeSolver calculation can be easily constructed using one of the previously defined wave ports.

# Convert wave port to mode solver
mode_solver = WP1.to_mode_solver(simulation=sim, freqs=np.linspace(f_min, f_max, 51))

We submit the mode solver job below.

mode_data = web.run(mode_solver, task_name="mode solver", path='./data/diff_stripline_mode.hdf5', verbose=False)

Because there are two terminals in the TerminalWavePort, the mode solver data will include two modes. The Ex and Ey field profiles of both modes are shown below.

# Plot mode field
fig, ax = plt.subplots(2, 1, figsize=(10, 4), tight_layout=True)
mode_solver.plot_field(field_name="Ey", val="real", mode_index=0, f=f0, ax=ax[0])
mode_solver.plot_field(field_name="Ey", val="real", mode_index=1, f=f0, ax=ax[1])
ax[0].set_title("Mode 0 (diff) | Re(Ey)")
ax[1].set_title("Mode 1 (comm) | Re(Ey)")
plt.show()

In the analysis below, we will concentrate on the differential mode (mode_index=0).

We extract the effective index neffn_{\text{eff}} and attenuation α\alpha from the mode_data.

# Gather alpha, neff from mode solver results
neff_mode = mode_data.modes_info["n eff"].isel(mode_index=0).squeeze()
alphadB_mode = mode_data.modes_info["loss (dB/cm)"].isel(mode_index=0).squeeze()

We obtain the differential impedance Z0Z_0 from the full Z-matrix below. The terminal_label_in and terminal_label_out indices specify which row and column of the Z-matrix to extract. The naming scheme is <diff_pair_name>@<diff OR comm>.

Z0_mode = mode_data.transmission_line_terminal_data.Z0.sel(
terminal_label_in="Diff0@diff", terminal_label_out="Diff0@diff"
)

Below, we plot the effective index, loss, and characteristic impedance.

fig, ax = plt.subplots(3,1, figsize=(8, 8), tight_layout=True, sharex=True)
ax[0].plot(neff_mode.f / 1e9, neff_mode)
ax[0].set_title("Effective index")
ax[0].set_ylabel("$n_{\\text{eff}}$")
ax[1].plot(alphadB_mode.f / 1e9, alphadB_mode)
ax[1].set_title("Attenuation")
ax[1].set_ylabel("$\\alpha$ (dB/cm)")
ax[2].plot(Z0_mode.f / 1e9, np.real(Z0_mode))
ax[2].set_title("Differential impedance")
ax[2].set_ylabel("$Z_0$ ($\\Omega$)")
ax[2].set_xlabel("f (GHz)")
for axis in ax:
axis.grid()
plt.show()

For a longitudinally-invariant transmission line, the 2D mode solution fully defines its propagation characteristics and the 3D solution is not needed. However, for demonstration purposes, we will perform the 3D FDTD analysis to obtain the differential S21 parameter SDD21. The line length is 4 inches, which is tens of wavelengths at 70 GHz.

S-parameter convergence typically requires a lower level of grid refinement than the 2D mode solution. We reduce the grid refinement below to cut down on computational cost.

# Update layer refinement with reduced fidelity
lr4 = lr1.updated_copy(
min_steps_along_axis=2,
corner_refinement=rf.GridRefinement(dl=t, num_cells=2),
)
# Update overall grid specification
grid_spec_3d = grid_spec.updated_copy(layer_refinement_specs=[lr2, lr3, lr4])

Since the transmission line is symmetric and there will be no coupling between the two modes, we only need to run the differential mode of one wave port WP1. This further reduces simulation cost.

# Update sim and TCM
sim_3d = sim.updated_copy(grid_spec=grid_spec_3d)
tcm_3d = tcm.updated_copy(simulation=sim_3d, run_only=["WP1@Diff0@diff"])

The job is submitted below.

tcm_data = web.run(tcm_3d, task_name="diff_stripline_3d", path="./data/diff_stripline.hdf5", verbose=False)

We use the port_in and port_out parameters to select the corresponding entry of the S-matrix. The naming scheme is <port_name>@<differential_pair_name>@<diff OR comm> for differential pairs. Below, we extract SDD21.

# Get S-parameters
s_matrix = tcm_data.smatrix()
SDD21 = np.conjugate(s_matrix.data.sel(port_in="WP1@Diff0@diff", port_out="WP2@Diff0@diff"))
SDD21dB = 20*np.log10(np.abs(SDD21))
# Plot
fig, ax = plt.subplots(figsize=(8, 4), tight_layout=True)
ax.plot(SDD21dB.f / 1e9, SDD21dB)
ax.set_title("Insertion loss")
ax.set_ylabel("dB")
ax.set_xlabel("f (GHz)")
ax.grid()
plt.show()

Below, we plot the Ey field profile recorded by the field monitor at max frequency.

fig, ax = plt.subplots(figsize=(10, 2), tight_layout=True)
# Plotting diff mode
sim_data = tcm_data.data["WP1@Diff0@diff"]
ey = sim_data["monitor 1"].Ey.sel(f=f_max, method="nearest")
np.real(ey).plot(x="z", y="x", ax=ax)
ax.set_title("Diff. mode | Re(Ey)")
ax.set_ylim(-30 * mil, 30 * mil)
plt.show()