---
title: Intro to Transmission Line Modeling
jupyter: python3
---

<center><img src="./img/cover_intro_transmission_line.png" width=480 /></center>

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.

```{python}
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'
```

## Building the Simulation

### Key Parameters

We begin by defining some key parameters. The design impedance $Z_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.

<center><img src="./img/differential_stripline_schematic.png" width=400 /></center>

```{python}
# Frequency range (Hz)
f_min, f_max = (1e9, 70e9)
f0 = (f_max + f_min) / 2
freqs = np.linspace(f_min, f_max, 101)
```

```{python}
# 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
```

```{python}
# Material properties
cond = 60  # Metal conductivity in S/um
eps = 4.4  # Relative permittivity, substrate
losstan = 0.0012  # Loss tangent, substrate
```

### Mediums and Structures

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.

```{python}
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")
```

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

```{python}
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.

```{python}
# 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
)
```

### Grid and Boundaries

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.

```{python}
# 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.

```{python}
# 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).

```{python}
boundary_spec = rf.BoundarySpec(
    x=rf.Boundary.pml(),
    y=rf.Boundary.pec(),
    z=rf.Boundary.pml(),
)
```

### Monitors

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

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

### Terminal Wave Port

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.

```{python}
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

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

```{python}
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.

```{python}
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`.

```{python}
# 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()
```

## 2D Analysis

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

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

```{python}
# 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.

```{python}
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.

```{python}
# 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 $n_{\text{eff}}$ and attenuation $\alpha$ from the `mode_data`.

```{python}
#| scrolled: true
# 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 $Z_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>`.

```{python}
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.

```{python}
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()
```

## 3D Analysis

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.

```{python}
# 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.

```{python}
# 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.

```{python}
#| scrolled: true
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`.

```{python}
# 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.

```{python}
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()
```

