Skip to content

Edge-mounted SMA to Co-planar Waveguide Transition

Download as:

The subminiature version A (SMA) coaxial connector is an essential component in printed circuit board (PCB) applications. It is commonly used as the interface between the on-board circuit and external components such as antennas or measurement devices.

In this notebook, we model two edge-mounted SMA connectors attached to a grounded co-planar waveguide (CPW). The connector-to-connector insertion and return losses are calculated to ensure proper impedance matching and minimal reflection.

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'
# Frequencies and bandwidth
(f_min, f_max) = (1e9, 10e9)
f0 = (f_min + f_max) / 2
freqs = np.linspace(f_min, f_max, 301)

Important geometry dimensions are defined below. The default length unit is microns, so we introduce a mm conversion factor.

mm = 1000 # Conversion factor mm to microns
# Coaxial dimensions (50 ohm)
R0 = 0.635 * mm # Coax inner radius
R1 = 2.125 * mm # Coax outer radius
# Substrate overall dimensions
H = 1.57 * mm # Substrate thickness
Lsub, Wsub = (83 * mm, 30 * mm) # PCB board dimensions
# Transmission line dimensions
T = 0.038 * mm # Metal thickness
WS = 2.58 * mm # Signal trace width
G = 1 * mm # CPW gap width
WG = 5 * mm # Side ground trace width
# Via dimensions
VR = 0.5 * mm # Via radius
VP = 3 * mm # Via pitch, longitudinal
VL = 3 * mm # Via pitch, transverse
VS = 1 * mm # Via start z-coordinate
# Edge mount dimensions
Voffset = 0.125 * mm # SMA connector vertical offset

Below, we define the materials used in the model:

  • PTFE for the SMA dielectric core
  • Gold for the SMA body
  • FR4 for the PCB substrate
  • Copper for the PCB traces
med_FR4 = rf.Medium(permittivity=4.4)
med_PTFE = rf.Medium(permittivity=2.1)
med_Cu = rf.LossyMetalMedium(conductivity=60, frequency_range=(f_min, f_max)) # [S/um]
med_Au = rf.LossyMetalMedium(conductivity=41, frequency_range=(f_min, f_max)) # [S/um]

The SMA geometry is imported from a STL file, and then translated and rotated to the appropriate position. The SMA core is created from a rf.Cylinder instance. To ensure a close fit, we initialize the SMA core to be slightly larger than the inner radius and use Boolean subtraction to cut it to size.

# Import SMA geometry
geom_SMA = rf.TriangleMesh.from_stl(filename="./misc/SMA_model.stl", scale=mm, origin=(0, 0, 0))
geom_SMA = (geom_SMA.rotated(np.pi / 2, 0)).rotated(np.pi, 2)
geom_SMA = geom_SMA.translated(0, Voffset, 0)
# Create SMA dielectric core
geom_SMA_diel = rf.Cylinder(
center=(0, Voffset, -6 * mm / 2), radius=1.1 * R1, length=6 * mm, axis=2
)
geom_SMA_diel -= geom_SMA

We also make a copy for a second connector.

# Make copy for second connector
geom_SMA2 = (geom_SMA.rotated(np.pi, 1)).translated(0, 0, Lsub)
geom_SMA_diel2 = (geom_SMA_diel.rotated(np.pi, 1)).translated(0, 0, Lsub)

The substrate and CPW geometries are created below.

# Substrate
geom_sub = rf.Box.from_bounds(rmin=(-Wsub / 2, -H - T, 0), rmax=(Wsub / 2, -T, Lsub))
# Transmission line and connecting structures
geom_sig = rf.Box.from_bounds(rmin=(-WS / 2, -T, 0), rmax=(WS / 2, 0, Lsub))
geom_gnd1 = rf.Box.from_bounds(rmin=(-WS / 2 - G - WG, -T, 0), rmax=(-WS / 2 - G, 0, Lsub))
geom_gnd2 = geom_gnd1.reflected((1, 0, 0))
geom_line = rf.GeometryGroup(geometries=[geom_sig, geom_gnd1, geom_gnd2])
geom_gnd = rf.Box.from_bounds(rmin=(-Wsub / 2, -H - 2 * T, 0), rmax=(Wsub / 2, -H - T, Lsub))

To ensure proper transmission in the high frequency range, we create a via fence that encloses the signal trace.

# Create via fence
def create_via_hole(xpos, zpos):
geom = rf.Cylinder(center=(xpos, -H / 2 - T, zpos), axis=1, length=H, radius=VR)
return geom
geom_via_array = []
zpos = VS
while zpos < Lsub - VS + 0.1 * mm:
for xpos in [-VL, VL]:
geom_via_array += [create_via_hole(xpos, zpos)]
zpos += VP
geom_via_group = rf.GeometryGroup(geometries=geom_via_array)

We combine the previously defined geometries and materials into Structure instances, ready for simulation.

# Create structures
str_SMA = rf.Structure(geometry=geom_SMA, medium=med_Au)
str_SMA2 = rf.Structure(geometry=geom_SMA2, medium=med_Au)
str_SMA_diel = rf.Structure(geometry=geom_SMA_diel, medium=med_PTFE)
str_SMA_diel2 = rf.Structure(geometry=geom_SMA_diel2, medium=med_PTFE)
str_sub = rf.Structure(geometry=geom_sub, medium=med_FR4)
str_line = rf.Structure(geometry=geom_line, medium=med_Cu)
str_gnd = rf.Structure(geometry=geom_gnd, medium=med_Cu)
str_vias = rf.Structure(geometry=geom_via_group, medium=med_Cu)
# List of all structures
structure_list = [
str_SMA,
str_SMA2,
str_SMA_diel,
str_SMA_diel2,
str_sub,
str_line,
str_gnd,
str_vias,
]

The simulation boundaries are open (PML) on all sides. We introduce a padding of wavelength/2 on all sides to ensure that the external boundaries do not encroach on the near-field.

# Define simulation size and center
padding = rf.C_0 / f0 / 2
sim_LZ = Lsub + padding
sim_LX = Wsub + padding
sim_LY = 5 * mm + padding
sim_center = (0, 0, Lsub / 2)

The grid refinement strategy is as follows:

  • Use LayerRefinementSpec for PCB metallic layers
  • Use MeshOverrideStructure for SMA dielectric core and metal via fences
# Define layer refinement spec
lr_options = {
"corner_refinement": rf.GridRefinement(dl=0.1 * mm, num_cells=2),
"min_steps_along_axis": 1,
}
lr1 = rf.LayerRefinementSpec.from_structures(structures=[str_line], **lr_options)
lr2 = rf.LayerRefinementSpec.from_structures(structures=[str_gnd], **lr_options)
# Define mesh override around SMA core and vias
rbox1 = rf.MeshOverrideStructure(
geometry=geom_SMA_diel.bounding_box, dl=(0.2 * mm, 0.2 * mm, 0.2 * mm)
)
rbox2 = rf.MeshOverrideStructure(
geometry=geom_SMA_diel2.bounding_box, dl=(0.2 * mm, 0.2 * mm, 0.2 * mm)
)
rbox_vias = []
for geom in geom_via_array:
rbox_vias += [
rf.MeshOverrideStructure(geometry=geom.bounding_box, dl=(0.3 * mm, None, 0.3 * mm))
]

The overall grid specification is defined below.

# Define overall grid specification
grid_spec = rf.GridSpec.auto(
min_steps_per_wvl=15,
wavelength=rf.C_0 / f_max,
layer_refinement_specs=[lr1, lr2],
override_structures=[rbox1, rbox2] + rbox_vias,
)

We define some field monitors for visualization purposes below.

# Field Monitor
mon_1 = rf.FieldMonitor(
center=(0, -T - H / 2, 0),
size=(rf.inf, 0, rf.inf),
freqs=[f_min, f0, f_max],
name="field in-plane",
)
mon_2 = rf.FieldMonitor(
center=(0, 0, 0),
size=(0, rf.inf, rf.inf),
freqs=[f_min, f0, f_max],
name="field cross section",
)
# List of all monitors
monitor_list = [mon_1, mon_2]

Wave ports are positioned along the coaxial section of each SMA connector.

# Wave port position and dimension
wp_offset = 4 * mm # longitudinal offset
w_port = 4.6 * mm # port width and height
# Define wave ports
WP1 = rf.WavePort(
center=(0, Voffset, -wp_offset),
size=(w_port, w_port, 0),
mode_spec=rf.MicrowaveModeSpec(target_neff=np.sqrt(2.1)),
direction="+",
name="WP1",
frame=None,
)
WP2 = WP1.updated_copy(
center=(0, Voffset, Lsub + wp_offset),
direction="-",
name="WP2",
)

Defining Simulation and TerminalComponentModeler

Section titled “Defining Simulation and TerminalComponentModeler”

The Simulation and TerminalComponentModeler instances are defined below.

sim = rf.Simulation(
center=sim_center,
size=(sim_LX, sim_LY, sim_LZ),
grid_spec=grid_spec,
structures=structure_list,
monitors=monitor_list,
run_time=5e-9,
plot_length_units="mm",
symmetry=(1, 0, 0),
)
tcm = rf.TerminalComponentModeler(
simulation=sim,
ports=[WP1, WP2],
freqs=freqs,
)

Before running, it is a good idea to check the structure layout and simulation grid. Below, the top and side cross-sections of the structure are shown, along with the wave port sources (green arrow) and internal modal absorbers (blue arrow).

fig, ax = plt.subplots(1, 2, figsize=(10, 10), tight_layout=True)
tcm.plot_sim(
y=-T, ax=ax[0], monitor_alpha=0, hlim=(-20 * mm, 20 * mm), vlim=(-15 * mm, Lsub + 15 * mm)
)
tcm.plot_sim(
x=0, ax=ax[1], monitor_alpha=0, hlim=(-10 * mm, 10 * mm), vlim=(-15 * mm, Lsub + 15 * mm)
)
plt.show()

The grid in the SMA-CPW transition region is shown below. The mesh override regions are boxed with dotted black lines.

fig, ax = plt.subplots(figsize=(10, 10), tight_layout=True)
tcm.simulation.plot_grid(y=-T - H / 2, ax=ax)
tcm.plot_sim(
y=-T - H / 2, ax=ax, monitor_alpha=0, hlim=(-20 * mm, 20 * mm), vlim=(-10 * mm, 10 * mm)
)
plt.show()

We can also visualize the setup in 3D.

sim.plot_3d()

The simulation is executed below.

tcm_data = web.run(tcm, task_name="sma_connector", path="data/sma_connector.hdf5", verbose=False)

The field monitor data is accessed from the .data attribute of the TerminalComponentModelerData result. The key is in the format <wave port name>@<mode number>, for example WP1@0. (In this case, there is only one mode.)

# Extract simulation data
sim_data = tcm_data.data["WP1@0"]

Below, the Ex and Ey field components are plotted along the top and side cross-sections respectively.

fig, ax = plt.subplots(1, 2, figsize=(10, 8), tight_layout=True)
f_plot = f_max
sim_data.plot_field("field in-plane", field_name="Ex", val="real", f=f_plot, ax=ax[0])
sim_data.plot_field("field cross section", "Ey", val="real", f=f_plot, ax=ax[1])
for axis in ax:
axis.set_ylim(-15 * mm, Lsub + 10 * mm)
axis.set_xlim(-20 * mm, 20 * mm)
plt.show()

The S-matrix data is extracted using the smatrix() method. To access a specific S_ij parameter, use the corresponding port_in and port_out attributes. Note the use of np.conjugate to convert the S-parameter from the physics phase convention (current Flex RF default) to the usual electrical engineering convention.

# Extract S-matrix and S-parameters
smat = tcm_data.smatrix()
S11 = np.conjugate(smat.data.isel(port_in=0, port_out=0))
S21 = np.conjugate(smat.data.isel(port_in=0, port_out=1))

The insertion and return losses are plotted below. We observe excellent transmission and minimal reflection across the frequency band, indicating that the impedance of the SMA connector is well-matched to that of the on-board transmission line.

fig, ax = plt.subplots(figsize=(10, 5), tight_layout=True)
ax.plot(freqs / 1e9, 20 * np.log10(np.abs(S21)), label="|S21|")
ax.plot(freqs / 1e9, 20 * np.log10(np.abs(S11)), label="|S11|")
ax.set_title("Insertion and return loss (dB)")
ax.set_xlabel("f (GHz)")
ax.set_ylabel("dB")
ax.legend()
ax.grid()
plt.show()