The following FPC configurations have been analyzed using TNT-MMTL, a free and open-source 2D field solver using electrostatic approximation and MoM.
TNT-MMTL is efficient but cannot simulate "true" floating conductors (such as an unconnected shield), the conductor must be either a signal conductor (albeit unexcited) or an ideal grounded conductor. Hence, openEMS is used to perform 3D full-wave simulations to cross-verify the shielded results.
Although the best effort was made to ensure the information provided is accurate, numerical modeling is not a replacement of electrical testing. You are solely responsible for designing, validating and testing your application to ensure compliance with applicable standards and design requirements.
Permission to use, copy, modify, and/or distribute this document for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
THE DOCUMENT IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS DOCUMENT INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR ACCURACY OF THIS DOCUMENT.
Layer | Material | Thickness (mm) | εr | Note |
---|---|---|---|---|
L1 | PEC | 0.012 | 0.018 mm for 0.5-oz boards | |
Core | Polymide | 0.050 | 3.3 | |
L2 | PEC | 0.012 |
- PEC = Perfect Electric Conductor (conductivity and thickness are ignored)
- Trace Width: 0.12 mm
- Trace Spacing: 0.2 mm

Characteristic Impedance (Ohms):
For Signal Line ::DiffpairR1= 46.8591
For Signal Line ::DiffpairR0= 46.8591
Characteristic Impedance Odd/Even (Ohms):
odd= 45.3897
even= 48.3253
Effective Dielectric Constant:
For Signal Line ::DiffpairR1= 2.53989
For Signal Line ::DiffpairR0= 2.53988
The odd-mode impedance is close to 45 Ω, the 2D MoM result is close to standard calculators. But this model is oversimplified, the additional dielectric layers and shields above will certaintly change the characteristic impedance slightly.
Layer | Material | Thickness (mm) | εr | Note |
---|---|---|---|---|
Coverlay | Polymide | 0.050 | 2.9 | |
Adhesive | ??? | 0.050 | 2, 3, 4 | εr unknown |
L1 | PEC | 0.012 | 0.018 mm for 0.5-oz boards | |
Core | Core Polymide | 0.050 | 3.3 | |
L2 | PEC | 0.012 |
- PEC = Perfect Electric Conductor (conductivity and thickness are ignored)
- Trace Width: 0.12 mm
- Trace Spacing: 0.2 mm

Characteristic Impedance (Ohms):
For Signal Line ::DiffpairR1= 44.0585
For Signal Line ::DiffpairR0= 44.0585
Characteristic Impedance Odd/Even (Ohms):
odd= 42.3364
even= 45.8197
Effective Dielectric Constant:
For Signal Line ::DiffpairR1= 2.87305
For Signal Line ::DiffpairR0= 2.87304
Characteristic Impedance (Ohms):
For Signal Line ::DiffpairR1= 42.5619
For Signal Line ::DiffpairR0= 42.5621
Characteristic Impedance Odd/Even (Ohms):
odd= 40.7518
even= 44.4339
Effective Dielectric Constant:
For Signal Line ::DiffpairR1= 3.07865
For Signal Line ::DiffpairR0= 3.07861
Characteristic Impedance (Ohms):
For Signal Line ::DiffpairR1= 41.3982
For Signal Line ::DiffpairR0= 41.398
Characteristic Impedance Odd/Even (Ohms):
odd= 39.5023
even= 43.38
Effective Dielectric Constant:
For Signal Line ::DiffpairR1= 3.25417
For Signal Line ::DiffpairR0= 3.25419
As a cross-verification, we perform a full-wave FDTD simulation using openEMS.
Each trace in the differential pair is excited one by one in 4 simulations, creating a 4-port network with 16 single-ended S-parameters - effectively a virtual 4-port VNA.
These parameters are then mathematically converted into differential-mode S-parameters.
Finally a TDR plot is computed to find the trace impedance.
According to this (poorly-meshed) openEMS simulation, the differential impedance is 87.48 Ω, which means the odd-mode impedance is 43.74 Ω. The difference between 2D MoM via TNT-MMTL and 3D FDTD is 3 Ω. If you must make a choice, you should probably trust 2D MoM more - 3D FDTD is sensitive to meshing,
The extra dielectric layer reduces the odd-mode impedance by approximately 5 Ω. Since USB 3.0's odd-mode impedance requirement is technically 45 Ω, if only a basic single-dielectric microstrip calculator is available, design for 50 Ω instead of 45 Ω to take the impedance reduction into account. Since USB traces are commonly already designed for 50 Ω for the sake of simplicify (managing one controlled-impedance trace only), this reduction actually improves compliance in this case.
Layer | Material | Thickness (mm) | εr | Note |
---|---|---|---|---|
Shield | PEC | 0.018 | ||
Coverlay | Polymide | 0.050 | 2.9 | |
Adhesive | ??? | 0.050 | 3 | εr unknown |
L1 | PEC | 0.012 | 0.018 mm for 0.5-oz boards | |
Core | Core Polymide | 0.050 | 3.3 | |
L2 | PEC | 0.012 |
- PEC = Perfect Electric Conductor (conductivity and thickness are ignored)
- Trace Width: 0.12 mm
- Trace Spacing: 0.2 mm

The simulator requires all traces to be either signal traces or ideal grounds. Thus we simulate the effect of a top shield using a wide 10 mm conductor, forming a three-conductor transmission line. But in this case, only single-ended impedance can be calculated:
Characteristic Impedance (Ohms):
For Signal Line ::shieldR2= 21.9386
For Signal Line ::DiffpairR1= 36.5294
For Signal Line ::DiffpairR0= 36.5288
Effective Dielectric Constant:
For Signal Line ::shieldR2= 2.65078
For Signal Line ::DiffpairR1= 3.15248
For Signal Line ::DiffpairR0= 3.1526
If the top shield is treated as an ideal grounded trace:
Characteristic Impedance (Ohms):
For Signal Line ::DiffpairR1= 35.0959
For Signal Line ::DiffpairR0= 35.0951
Characteristic Impedance Odd/Even (Ohms):
odd= 34.9531
even= 35.2392
Effective Dielectric Constant:
For Signal Line ::DiffpairR1= 3.15254
For Signal Line ::DiffpairR0= 3.15252
If the top shield is treated as an ideal ground plane:
Characteristic Impedance (Ohms):
For Signal Line ::DiffpairR1= 35.0957
For Signal Line ::DiffpairR0= 35.0954
Characteristic Impedance Odd/Even (Ohms):
odd= 34.9529
even= 35.2391
Effective Dielectric Constant:
For Signal Line ::DiffpairR1= 3.15253
For Signal Line ::DiffpairR0= 3.15257
TNT-MMTL can't simulate the differential impedance of floating conductors. From first principles of electromagnetism, we have strong reasons to believe that a metal plane changes the impedance of a transmission line regardless of whether it's floating or grounded - but as further confirmation, we perform a full-wave simulation of a shielded differential pair, with the shield unexcited.
Each trace in the differential pair is excited one by one in 4 simulations, creating a 4-port network with 16 single-ended S-parameters - effectively a virtual 4-port VNA.
These parameters are then mathematically converted into differential-mode S-parameters.
Finally a TDR plot is computed to find the trace impedance.
According to this openEMS simulation, the differential impedance is 77.62 Ω, which means the odd-mode impedance is 38.81 Ω. There's a 3.86 Ω difference between 2D MoM via TNT-MMTL and 3D FDTD via openEMS.
The top layer essentially converts the differential microstrip pair into a differential stripline, with a dramatic impedance reduction from 45 Ω to 35-38.81 Ω. This has been showed via TNT-MMTL and confirmed qualitatively via openEMS. The shield can't be used as-is. In order to bring odd-mode impedance back to the nominal value 0.0762 mm (3 mils) traces must be used. According to TNT-MMTL, its odd-mode impedance is 45 Ω. According to openEMS, its odd-mode impedance is 51.35 Ω. But this is done at the expense of hitting the minimum trace width design rule, and also with higher conductor loss (not simulated).
openEMS overestimates impedance while TNT-MMTL underestimates impedance. Due to the lack of experimental data, it can be a better idea to target their average in openEMS: 47.2 Ω. This corresponds to a 0.088 mm (0.2 mm spacing) differential pair. According to TNT-MMTL, it has an impedance of 42.93 Ω. Both values are within +/- 2.2 Ω of the nominal 45 Ω impedance, ensuring compliance regardless of whether the shield is completely floating, truly connected (via electrical connections on the FPC), or partially connected (via the connector shield or external chassis).
Alternatively, perhaps moving from microstrips to a non-standard variant of the coplanar waveguide can help providing higher impedances without increaing the conductor loss. The ungrounded variant can be used: without a full ground plane directly underneath. the ground layer is only used to route bridges to join the left and right grounds. This can be an interesting long-term research project for future designs. Since no analytical solution is available, many simulations are required.
Raw simulation inputs are available under the CC-0 license to maximize transparency and reproducibility.
Note: TNT-MMTL is no longer maintained, and requiren numerous patches (by NetBSD) to build from scratch. I have a fork, with known-good autotools build (the introduction mentions WebAssembly, but it works natively too, ignore the WebAssembly build instructions). It requires Tcl/Tk, bwidget, itcl, and bwidget to run.
#----------------------------------
# File: /home/user/code/fpc-impedance/sim1.xsctn
# Fri Jul 11 14:45:39 UTC 2025
#----------------------------------
package require csdl
set _title ""
set ::Stackup::couplingLength "0.0"
set ::Stackup::riseTime "0.0"
set ::Stackup::frequency "1e9"
set ::Stackup::defaultLengthUnits "microns"
set CSEG 300
set DSEG 300
GroundPlane GrouPlan1 \
-thickness 12 \
-yOffset 0.0 \
-xOffset 0.0
DielectricLayer Polymide \
-thickness 50 \
-lossTangent 0.0 \
-permittivity 3.3 \
-permeability 1.0 \
-yOffset 0.0 \
-xOffset 0.0
RectangleConductors Diffpair \
-width 120 \
-pitch 320 \
-conductivity 5.0e7S/m \
-height 12 \
-number 2 \
-yOffset 0.0 \
-xOffset 0.0
#----------------------------------
# File: /home/user/code/fpc-impedance/sim2.xsctn
# Fri Jul 11 14:46:07 UTC 2025
#----------------------------------
package require csdl
set _title ""
set ::Stackup::couplingLength "0.0"
set ::Stackup::riseTime "0.0"
set ::Stackup::frequency "1e9"
set ::Stackup::defaultLengthUnits "microns"
set CSEG 300
set DSEG 300
GroundPlane GrouPlan1 \
-thickness 12 \
-yOffset 0.0 \
-xOffset 0.0
DielectricLayer Core \
-thickness 50 \
-lossTangent 0.0 \
-permittivity 3.3 \
-permeability 1.0 \
-yOffset 0.0 \
-xOffset 0.0
RectangleConductors Diffpair \
-width 120 \
-pitch 320 \
-conductivity 5.0e7S/m \
-height 12 \
-number 2 \
-yOffset 0.0 \
-xOffset 0
DielectricLayer Adhesive \
-thickness 50 \
-lossTangent 0.0 \
-permittivity 2 \
-permeability 1.0 \
-yOffset 0.0 \
-xOffset 0.0
DielectricLayer Coverlay \
-thickness 50 \
-lossTangent 0.0 \
-permittivity 2.9 \
-permeability 1.0 \
-yOffset 0.0 \
-xOffset 0.0
#----------------------------------
# File: /home/user/code/fpc-impedance/sim4_tracesignal.xsctn
# Fri Jul 11 14:49:47 UTC 2025
#----------------------------------
package require csdl
set _title ""
set ::Stackup::couplingLength "0.0"
set ::Stackup::riseTime "0.0"
set ::Stackup::frequency "1e9"
set ::Stackup::defaultLengthUnits "microns"
set CSEG 500
set DSEG 500
GroundPlane GrouPlan1 \
-thickness 12 \
-yOffset 0.0 \
-xOffset 0.0
DielectricLayer Core \
-thickness 50 \
-lossTangent 0.0 \
-permittivity 3.3 \
-permeability 1.0 \
-yOffset 0.0 \
-xOffset 0.0
RectangleConductors Diffpair \
-width 120 \
-pitch 320 \
-conductivity 5.0e7S/m \
-height 12 \
-number 2 \
-yOffset 0.0 \
-xOffset 300
DielectricLayer Adhesive \
-thickness 50 \
-lossTangent 0.0 \
-permittivity 3 \
-permeability 1.0 \
-yOffset 0.0 \
-xOffset 0.0
DielectricLayer Coverlay \
-thickness 50 \
-lossTangent 0.0 \
-permittivity 2.9 \
-permeability 1.0 \
-yOffset 0.0 \
-xOffset 0.0
RectangleConductors shield \
-width 1000 \
-pitch 1.0 \
-conductivity 5.0e7S/m \
-height 18 \
-number 1 \
-yOffset 0.0 \
-xOffset 0.0
#----------------------------------
# File: /home/user/code/fpc-impedance/sim4_traceground.xsctn
# Fri Jul 11 14:50:56 UTC 2025
#----------------------------------
package require csdl
set _title ""
set ::Stackup::couplingLength "0.0"
set ::Stackup::riseTime "0.0"
set ::Stackup::frequency "1e9"
set ::Stackup::defaultLengthUnits "microns"
set CSEG 500
set DSEG 500
GroundPlane GrouPlan1 \
-thickness 12 \
-yOffset 0.0 \
-xOffset 0.0
DielectricLayer Core \
-thickness 50 \
-lossTangent 0.0 \
-permittivity 3.3 \
-permeability 1.0 \
-yOffset 0.0 \
-xOffset 0.0
RectangleConductors Diffpair \
-width 120 \
-pitch 320 \
-conductivity 5.0e7S/m \
-height 12 \
-number 2 \
-yOffset 0.0 \
-xOffset 300
DielectricLayer Adhesive \
-thickness 50 \
-lossTangent 0.0 \
-permittivity 3 \
-permeability 1.0 \
-yOffset 0.0 \
-xOffset 0.0
DielectricLayer Coverlay \
-thickness 50 \
-lossTangent 0.0 \
-permittivity 2.9 \
-permeability 1.0 \
-yOffset 0.0 \
-xOffset 0.0
RectangleConductors grshield \
-width 1000 \
-pitch 1.0 \
-conductivity 5.0e7S/m \
-height 18 \
-number 1 \
-yOffset 0.0 \
-xOffset 0.0
#----------------------------------
# File: /home/user/code/fpc-impedance/sim4_plane.xsctn
# Fri Jul 11 21:14:28 UTC 2025
#----------------------------------
package require csdl
set _title ""
set ::Stackup::couplingLength "0.0"
set ::Stackup::riseTime "0.0"
set ::Stackup::frequency "1e9"
set ::Stackup::defaultLengthUnits "microns"
set CSEG 500
set DSEG 500
GroundPlane GrouPlan1 \
-thickness 12 \
-yOffset 0.0 \
-xOffset 0.0
DielectricLayer Core \
-thickness 50 \
-lossTangent 0.0 \
-permittivity 3.3 \
-permeability 1.0 \
-yOffset 0.0 \
-xOffset 0.0
RectangleConductors Diffpair \
-width 120 \
-pitch 320 \
-conductivity 5.0e7S/m \
-height 12 \
-number 2 \
-yOffset 0.0 \
-xOffset 0
DielectricLayer Adhesive \
-thickness 50 \
-lossTangent 0.0 \
-permittivity 3 \
-permeability 1.0 \
-yOffset 0.0 \
-xOffset 0.0
DielectricLayer Coverlay \
-thickness 50 \
-lossTangent 0.0 \
-permittivity 2.9 \
-permeability 1.0 \
-yOffset 0.0 \
-xOffset 0.0
GroundPlane Shield \
-thickness 18 \
-yOffset 0.0 \
-xOffset 0.0
Note that this simulation is poorly coded and poorly meshed mesh
has been significantly improved, though refactoring is still required, both
requires refactoring if you ever use it in a research report in a non-draft
form.
#!/usr/bin/env python3
import os
import sys
import pathlib
import numpy as np
import CSXCAD
from CSXCAD.SmoothMeshLines import SmoothMeshLines
import openEMS
from openEMS.physical_constants import C0
# Constant Section
# common constants and settings used for simulation.
# defined in CSXCAD/CSProperties.h, enum PropertyType.
# not exposed to Python yet.
CSX_EXCITATION_TYPEID = 0x08
CSX_PROBE_TYPEID = 0x10
unit = 1e-3 # all units in millimeters
f_min = 1e6 # for post-processing only, not used in simulations
f_max = 20e9 # determine the excitation bandwidth
points = 1001 # for post-processing only, not used in simulations
epsilon_r = 3.3 # permittivity target for calculating the mesh size
v = C0 / np.sqrt(epsilon_r)
wavelength = v / f_max / unit # convert to millimeters
# global parameters in the circuit board simulation
board_size = [50, 5] # 50 mm x 5 mm
# Set grid's base and high resolution. High resolution is
# used around the substrate and traces.
res = [wavelength / 20, wavelength / 50, wavelength / 50]
highres = [res[0] / 1.5, res[1] / 20, res[2] / 20]
freq_list = np.linspace(f_min, f_max, points)
port_list = [1, 2, 3, 4]
z0 = 50
# determine the simulation output path
filepath = pathlib.Path(__file__)
# name a directory without ".py"
projectdir = filepath.with_suffix("")
projectdir.mkdir(parents=True, exist_ok=True)
# find the filename of the script itself, and replace ".py" with ".xml"
xmlname = filepath.with_suffix(".xml").name
stackup_list = {
"Unshielded": {
"name": "Unshielded-FPC",
"layers": [
{"name": "Coverlay", "type": "dielectric", "thickness": 50e-3, "epsilon_r": 2.9},
{"name": "Adhesive", "type": "dielectric", "thickness": 50e-3, "epsilon_r": 3.0},
{"name": "L1", "type": "signal", "thickness": 0 },
{"name": "Core", "type": "dielectric", "thickness": 50e-3, "epsilon_r": 3.3},
{"name": "L2", "type": "plane", "thickness": 0 },
],
"z_initial": 100e-3,
"microstrip_width": 0.12,
"diffpair_gap": 0.2
},
"Shielded": {
"name": "Shielded-FPC",
"layers": [
{"name": "Shield", "type": "plane", "thickness": 0 },
{"name": "Coverlay", "type": "dielectric", "thickness": 50e-3, "epsilon_r": 2.9},
{"name": "Adhesive", "type": "dielectric", "thickness": 50e-3, "epsilon_r": 3.0},
{"name": "L1", "type": "signal", "thickness": 0 },
{"name": "Core", "type": "dielectric", "thickness": 50e-3, "epsilon_r": 3.3},
{"name": "L2", "type": "plane", "thickness": 0 },
],
"z_initial": 100e-3,
"microstrip_width": 0.12,
"diffpair_gap": 0.2
},
"Shielded-Narrow": {
"name": "Shielded-FPC-Narrow",
"layers": [
{"name": "Shield", "type": "plane", "thickness": 0 },
{"name": "Coverlay", "type": "dielectric", "thickness": 50e-3, "epsilon_r": 2.9},
{"name": "Adhesive", "type": "dielectric", "thickness": 50e-3, "epsilon_r": 3.0},
{"name": "L1", "type": "signal", "thickness": 0 },
{"name": "Core", "type": "dielectric", "thickness": 50e-3, "epsilon_r": 3.3},
{"name": "L2", "type": "plane", "thickness": 0 },
],
"z_initial": 100e-3,
"microstrip_width": 0.0762,
"diffpair_gap": 0.2
},
"Shielded-Narrow-Tuned": {
"name": "Shielded-FPC-Narrow-Tuned",
"layers": [
{"name": "Shield", "type": "plane", "thickness": 0 },
{"name": "Coverlay", "type": "dielectric", "thickness": 50e-3, "epsilon_r": 2.9},
{"name": "Adhesive", "type": "dielectric", "thickness": 50e-3, "epsilon_r": 3.0},
{"name": "L1", "type": "signal", "thickness": 0 },
{"name": "Core", "type": "dielectric", "thickness": 50e-3, "epsilon_r": 3.3},
{"name": "L2", "type": "plane", "thickness": 0 },
],
"z_initial": 100e-3,
"microstrip_width": 0.088,
"diffpair_gap": 0.2
}
}
def generate_pcb(csx, stackup, length, width):
z = stackup["z_initial"]
mesh_z = []
for layer in stackup["layers"]:
name = "%s-%s" % (stackup["name"], layer["name"])
if layer["type"] == "dielectric":
substrate = csx.AddMaterial(name, epsilon=layer["epsilon_r"])
substrate.SetColor("#BEBE82")
substrate.AddBox(
[-length / 2, -width / 2, z],
[ length / 2, width / 2, z - layer["thickness"]],
priority=0
)
if layer["type"] == "plane":
ground = csx.AddMetal(name)
ground.SetColor("#FFD700")
ground.AddBox(
start = [-length / 2, -width / 2, z],
stop = [ length / 2, width / 2, z - layer["thickness"]],
priority=5
)
if layer["thickness"] == 0:
mesh_z += [z]
layer["z"] = z
z -= layer["thickness"]
mesh_z += list(SmoothMeshLines([z, stackup["z_initial"]], highres[2]))
global_mesh = csx.GetGrid()
global_mesh.AddLine('x', [-length / 2, length / 2])
global_mesh.AddLine('y', [-width / 2, width / 2])
global_mesh.AddLine('z', mesh_z)
def generate_trace_with_port(fdtd, csx, number, start, stop, trace_y, plane_z, width, active=False):
foil = csx.AddMetal("port%d" % number)
foil.SetColor("#FFD700")
start = [start, -width / 2 + trace_y, 0]
stop = [stop, width / 2 + trace_y, plane_z]
feed_shift = 0
if active:
feed_shift = 10 * res[0]
meas_shift = 30 * res[0]
port = fdtd.AddMSLPort(
number, foil, start, stop, 'x', 'z',
FeedShift=feed_shift,
MeasPlaneShift=meas_shift,
excite=active,
priority=5
)
mesh = csx.GetGrid()
mesh.AddLine('y', [
trace_y + (-width / 2 + highres[1] * 1/3),
trace_y + (-width / 2 - highres[1] * 2/3),
trace_y + (width / 2 - highres[1] * 1/3),
trace_y + (width / 2 + highres[1] * 2/3),
])
mesh.AddLine('y',
SmoothMeshLines([
trace_y + (-width / 2 + highres[1] * 1/3),
trace_y + (width / 2 - highres[1] * 1/3)
], highres[1]
))
return port
def generate_structure(fdtd, csx, stackup):
generate_pcb(csx, stackup, length=board_size[0], width=board_size[1])
mesh = csx.GetGrid()
mesh.SetDeltaUnit(unit)
# X-axis must be meshed early since port creation depends on
# X mesh lines to place internal probes
mesh.SmoothMeshLines('x', res[0])
trace_start = -board_size[0] / 2
trace_stop = board_size[0] / 2
# use last layer as reference plane
plane_z = stackup["layers"][-1]["z"]
line_width = stackup["microstrip_width"]
port1 = generate_trace_with_port(
fdtd, csx, 1,
-board_size[0] / 2, 0,
-stackup["diffpair_gap"] / 2 - stackup["microstrip_width"] / 2,
plane_z,
line_width, active=True
)
port2 = generate_trace_with_port(
fdtd, csx, 2,
board_size[0] / 2, 0,
-stackup["diffpair_gap"] / 2 - stackup["microstrip_width"] / 2,
plane_z,
line_width, active=True
)
port3 = generate_trace_with_port(
fdtd, csx, 3,
-board_size[0] / 2, 0,
stackup["diffpair_gap"] / 2 + stackup["microstrip_width"] / 2,
plane_z,
line_width, active=True
)
port4 = generate_trace_with_port(
fdtd, csx, 4,
board_size[0] / 2, 0,
stackup["diffpair_gap"] / 2 + stackup["microstrip_width"] / 2,
plane_z,
line_width, active=True
)
port_list = [port1, port2, port3, port4]
# Y and Z axes must be smoothed late since more mesh lines are
# added during port creation.
mesh.SmoothMeshLines('y', res[1])
mesh.AddLine('z', [-1, 3])
mesh.SmoothMeshLines('z', res[1])
return port_list
def generate_tasks():
"""
Generate and return several "tasks" used for simulation. Each "task"
is a self-contained structure for simulation. Its output can be used
by the main logic to schedule simulations.
This function is project-specific.
"""
# Use PML_8 on the beginning and end of the X axis to provide
# high-quality termination resistance for microstrip transmission
# lines. Use MUR for faster termination around the non-critical
# faces of the simulation box
task_list = [
{
"name": "shielded-fpc",
"func": generate_structure,
"kwargs": {
"stackup": stackup_list["Shielded"],
},
"bc": ['PML_8', 'PML_8', 'MUR', 'MUR', 'MUR', 'MUR'],
},
{
"name": "unshielded-fpc",
"func": generate_structure,
"kwargs": {
"stackup": stackup_list["Unshielded"],
},
"bc": ['PML_8', 'PML_8', 'MUR', 'MUR', 'MUR', 'MUR'],
},
{
"name": "shielded-fpc-narrow",
"func": generate_structure,
"kwargs": {
"stackup": stackup_list["Shielded-Narrow"],
},
"bc": ['PML_8', 'PML_8', 'MUR', 'MUR', 'MUR', 'MUR'],
},
{
"name": "shielded-fpc-narrow-tuned",
"func": generate_structure,
"kwargs": {
"stackup": stackup_list["Shielded-Narrow-Tuned"],
},
"bc": ['PML_8', 'PML_8', 'MUR', 'MUR', 'MUR', 'MUR'],
},
]
for task in task_list:
# XXX: openEMS's Python binding contains bugs in the reset logic.
# Thus, each new openEMS instance MUST be associated with a CSX
# instance only ONCE via SetCSX(). Changing CSX via fdtd.SetCSX()
# may cause segmentation faults and hard-to-debug erroneous
# simulation results due to stale state.
newfdtd = openEMS.openEMS()
newcsx = CSXCAD.ContinuousStructure()
newfdtd.SetCSX(newcsx)
task["csx"] = newcsx
task["fdtd"] = newfdtd
generate_task_structure = task["func"]
port_list = generate_task_structure(newfdtd, newcsx, **task["kwargs"])
task["simdir"] = projectdir / ("%s" % task["name"])
# A simulation task can be a single-port structure or a multi-port
# structure, for a multi-port structure, multiple simulations are
# required with one active at a time.
task["port_count"] = max(1, len(newcsx.GetPropertyByType(CSX_EXCITATION_TYPEID)))
task["port_list"] = port_list
task["subsimdir"] = []
for port_nr in range(1, 1 + task["port_count"]):
task["subsimdir"].append(task["simdir"] / str(port_nr))
return task_list
def write_tasks(task_list):
"""
For each task in the task_list, generate the CSXCAD 3D models and
write them to the disk.
"""
for task in task_list:
print("Writing %s to %s" % (task["name"], task["simdir"]))
for subsimdir in task["subsimdir"]:
subsimdir.mkdir(parents=True, exist_ok=True)
xmlname = "model.xml"
task["csx"].Write2XML(subsimdir / xmlname)
def simulate_tasks(task_list):
"""
For each task in the task_list, determine output paths, setup boundary
conditions, excitation signals, and finally run the simulator for each
task.
"""
def toggle_excitations(csx, active_port_nr):
exc_list = csx.GetPropertyByType(CSX_EXCITATION_TYPEID)
for exc in exc_list:
if exc.GetName() != "port_excite_%d" % active_port_nr:
# disable the remaining inactive excitations
exc.SetEnabled(0)
else:
exc.SetEnabled(1)
for task in task_list:
fdtd = task["fdtd"]
fdtd.SetBoundaryCond(task["bc"])
fdtd.SetGaussExcite(f_max / 2, f_max / 2)
for idx, subsimdir in enumerate(task["subsimdir"]):
port_nr = idx + 1
toggle_excitations(task["csx"], port_nr)
print("Simulating %s (port %d) in %s" % (
task["name"], port_nr, subsimdir.as_posix()
))
# summon the ghost of Maxwell
fdtd.Run(subsimdir)
def postproc_tasks(task_list):
"""
Process the data generated by a complete simulation. Only knowledge of probes
are necessary. This function is agnostic about the structure and simulator
parameters.
"""
import skrf
from matplotlib import pyplot as plt
for task in task_list:
csx = task["csx"]
subsimdir = task["subsimdir"]
port_list = task["port_list"]
port_idx_list = range(0, task["port_count"])
s_all = np.zeros((len(freq_list), 4, 4), dtype=complex)
for src_port_idx in port_idx_list:
for p in port_list:
p.CalcPort(subsimdir[src_port_idx], freq_list, ref_impedance=z0)
for dst_port_idx in port_idx_list:
s = (
port_list[dst_port_idx].uf_ref /
port_list[src_port_idx].uf_inc
)
for f_idx in range(len(freq_list)):
s_all[f_idx,dst_port_idx,src_port_idx] = s[f_idx]
freq = skrf.Frequency.from_f(freq_list, unit='Hz')
network = skrf.Network(frequency=freq, s=s_all)
fig, axes = plt.subplots(4, 4, sharex=True, figsize=(14, 8))
for m in range(4):
for n in range(4):
network.plot_s_db(m=m, n=n, ax=axes[m][n])
plt.suptitle('Single-Ended S-parameters: %s' % task["name"])
plt.tight_layout()
plt.show()
# convert single-ended to mixed-mode S-parameters
network.renumber([0, 1, 2, 3], [0, 2, 1, 3])
network.se2gmm(p=2)
# keep only differential, drop common mode.
network = network.subnetwork([0, 1])
fig, axes = plt.subplots(2, 2, sharex=True, figsize=(14, 8))
plt.suptitle('Differential S-parameters: %s' % task["name"])
for m in range(2):
for n in range(2):
network.plot_s_db(m=m, n=n, ax=axes[m][n])
plt.tight_layout()
plt.show()
network_dc = network.extrapolate_to_dc(kind='linear')
plt.title('Differential TDR: %s' % task["name"])
if task["name"].startswith("shielded-fpc"):
plt.yticks(np.arange(70, 105, 1.5))
elif task["name"] == "unshielded-fpc":
plt.yticks(np.arange(88, 100, 1))
else:
assert False
network_dc.s11.plot_z_time_step(window='hamming', label="impedance")
plt.tight_layout()
plt.show()
def project_deembedding():
"""
Run project-specific de-embedding to obtain the final data.
"""
pass
def main(cmd):
task_list = generate_tasks()
if cmd in ["generate", "simulate"]:
write_tasks(task_list)
if cmd == "simulate":
simulate_tasks(task_list)
elif cmd == "postproc":
postproc_tasks(task_list)
project_deembedding()
else:
print("Unknown command %s" % cmd)
exit(1)
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: %s generate" % sys.argv[0])
print(" %s simulate" % sys.argv[0])
print(" %s postproc" % sys.argv[0])
exit(0)
main(sys.argv[1])