EDB: fully parametrized design#

This example shows how to use the EDB interface along with HFSS 3D Layout to create and solve a parameterized layout. The layout shows a differential via transition on a printed circuit board with back-to-back microstrip to stripline transitions. The model is fully parameterized to enable investigation of the transition performance on the many degrees of freedom.

The resulting model is shown below

d8f4a5fd7d824450b9658401cd6babe2

[1]:
import os
import tempfile

import pyaedt

import pyedb

Set non-graphical mode#

Set non-graphical mode. The default is False, which opens the AEDT UI.

[2]:
non_graphical = False

Launch EDB.#

[3]:
temp_dir = tempfile.TemporaryDirectory(suffix=".ansys")
aedb_path = os.path.join(temp_dir.name, "pcb.aedb")

# Select EDB version (change it manually if needed, e.g. "2024.1")
edb_version = "2024.1"
print(f"EDB version: {edb_version}")

edb = pyedb.Edb(edbpath=aedb_path, edbversion=edb_version)
EDB version: 2024.1
PyAEDT INFO: Logger is initialized in EDB.
PyAEDT INFO: legacy v0.21.1
PyAEDT INFO: Python version 3.10.11 (tags/v3.10.11:7d4cc5a, Apr  5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)]
PyAEDT INFO: EDB C:\Users\ansys\AppData\Local\Temp\tmp87_os07x.ansys\pcb.aedb created correctly.
PyAEDT INFO: EDB initialized.

Define the parameters.

[4]:
params = {
    "$ms_width": "0.4mm",
    "$sl_width": "0.2mm",
    "$ms_spacing": "0.2mm",
    "$sl_spacing": "0.1mm",
    "$via_spacing": "0.5mm",
    "$via_diam": "0.3mm",
    "$pad_diam": "0.6mm",
    "$anti_pad_diam": "0.7mm",
    "$pcb_len": "15mm",
    "$pcb_w": "5mm",
    "$x_size": "1.2mm",
    "$y_size": "1mm",
    "$corner_rad": "0.5mm",
}

for par_name in params:
    edb.add_project_variable(par_name, params[par_name])

Define the stackup layers from bottom to top.

[5]:
layers = [
    {"name": "bottom", "layer_type": "signal", "thickness": "35um", "material": "copper"},
    {"name": "diel_3", "layer_type": "dielectric", "thickness": "275um", "material": "FR4_epoxy"},
    {"name": "sig_2", "layer_type": "signal", "thickness": "35um", "material": "copper"},
    {"name": "diel_2", "layer_type": "dielectric", "thickness": "275um", "material": "FR4_epoxy"},
    {"name": "sig_1", "layer_type": "signal", "thickness": "35um", "material": "copper"},
    {"name": "diel_1", "layer_type": "dielectric", "thickness": "275um", "material": "FR4_epoxy"},
    {"name": "top", "layer_type": "signal", "thickness": "35um", "material": "copper"},
]

Create the EDB stackup. Define the bottom layer

[6]:
prev = None
for layer in layers:
    edb.stackup.add_layer(
        layer["name"],
        base_layer=prev,
        layer_type=layer["layer_type"],
        thickness=layer["thickness"],
        material=layer["material"],
    )
    prev = layer["name"]
Material 'copper' does not exist in material library. Intempt to create it from syslib.
Material 'FR4_epoxy' does not exist in material library. Intempt to create it from syslib.

Create a parametrized padstack for the signal via.

[7]:
signal_via_padstack = "automated_via"
edb.padstacks.create(
    padstackname=signal_via_padstack,
    holediam="$via_diam",
    paddiam="$pad_diam",
    antipaddiam="",
    antipad_shape="Bullet",
    x_size="$x_size",
    y_size="$y_size",
    corner_radius="$corner_rad",
    start_layer=layers[-1]["name"],
    stop_layer=layers[-3]["name"],
)
PyAEDT INFO: Padstack automated_via create correctly
[7]:
'automated_via'

Assign net names. There are only two signal nets.

[8]:
net_p = "p"
net_n = "n"

Place the signal vias.

[9]:
edb.padstacks.place(
    position=["$pcb_len/3", "($ms_width+$ms_spacing+$via_spacing)/2"],
    definition_name=signal_via_padstack,
    net_name=net_p,
    via_name="",
    rotation=90.0,
)
[9]:
<pyedb.dotnet.edb_core.edb_data.padstacks_data.EDBPadstackInstance at 0x1fa93e314b0>
[10]:
edb.padstacks.place(
    position=["2*$pcb_len/3", "($ms_width+$ms_spacing+$via_spacing)/2"],
    definition_name=signal_via_padstack,
    net_name=net_p,
    via_name="",
    rotation=90.0,
)
[10]:
<pyedb.dotnet.edb_core.edb_data.padstacks_data.EDBPadstackInstance at 0x1fa93e38770>
[11]:
edb.padstacks.place(
    position=["$pcb_len/3", "-($ms_width+$ms_spacing+$via_spacing)/2"],
    definition_name=signal_via_padstack,
    net_name=net_n,
    via_name="",
    rotation=-90.0,
)
[11]:
<pyedb.dotnet.edb_core.edb_data.padstacks_data.EDBPadstackInstance at 0x1fa93f58fe0>
[12]:
edb.padstacks.place(
    position=["2*$pcb_len/3", "-($ms_width+$ms_spacing+$via_spacing)/2"],
    definition_name=signal_via_padstack,
    net_name=net_n,
    via_name="",
    rotation=-90.0,
)
[12]:
<pyedb.dotnet.edb_core.edb_data.padstacks_data.EDBPadstackInstance at 0x1fa93f5b130>

Draw parametrized traces#

Trace width and the routing (Microstrip-Stripline-Microstrip). Applies to both p and n nets.

[13]:
# Trace width, n and p
width = ["$ms_width", "$sl_width", "$ms_width"]
# Routing layer, n and p
route_layer = [layers[-1]["name"], layers[4]["name"], layers[-1]["name"]]

Define points for three traces in the “p” net

[14]:
points_p = [
    [
        ["0.0", "($ms_width+$ms_spacing)/2"],
        ["$pcb_len/3-2*$via_spacing", "($ms_width+$ms_spacing)/2"],
        ["$pcb_len/3-$via_spacing", "($ms_width+$ms_spacing+$via_spacing)/2"],
        ["$pcb_len/3", "($ms_width+$ms_spacing+$via_spacing)/2"],
    ],
    [
        ["$pcb_len/3", "($ms_width+$sl_spacing+$via_spacing)/2"],
        ["$pcb_len/3+$via_spacing", "($ms_width+$sl_spacing+$via_spacing)/2"],
        ["$pcb_len/3+2*$via_spacing", "($sl_width+$sl_spacing)/2"],
        ["2*$pcb_len/3-2*$via_spacing", "($sl_width+$sl_spacing)/2"],
        ["2*$pcb_len/3-$via_spacing", "($ms_width+$sl_spacing+$via_spacing)/2"],
        ["2*$pcb_len/3", "($ms_width+$sl_spacing+$via_spacing)/2"],
    ],
    [
        ["2*$pcb_len/3", "($ms_width+$ms_spacing+$via_spacing)/2"],
        ["2*$pcb_len/3+$via_spacing", "($ms_width+$ms_spacing+$via_spacing)/2"],
        ["2*$pcb_len/3+2*$via_spacing", "($ms_width+$ms_spacing)/2"],
        ["$pcb_len", "($ms_width+$ms_spacing)/2"],
    ],
]

Define points for three traces in the “n” net

[15]:
points_n = [
    [
        ["0.0", "-($ms_width+$ms_spacing)/2"],
        ["$pcb_len/3-2*$via_spacing", "-($ms_width+$ms_spacing)/2"],
        ["$pcb_len/3-$via_spacing", "-($ms_width+$ms_spacing+$via_spacing)/2"],
        ["$pcb_len/3", "-($ms_width+$ms_spacing+$via_spacing)/2"],
    ],
    [
        ["$pcb_len/3", "-($ms_width+$sl_spacing+$via_spacing)/2"],
        ["$pcb_len/3+$via_spacing", "-($ms_width+$sl_spacing+$via_spacing)/2"],
        ["$pcb_len/3+2*$via_spacing", "-($ms_width+$sl_spacing)/2"],
        ["2*$pcb_len/3-2*$via_spacing", "-($ms_width+$sl_spacing)/2"],
        ["2*$pcb_len/3-$via_spacing", "-($ms_width+$sl_spacing+$via_spacing)/2"],
        ["2*$pcb_len/3", "-($ms_width+$sl_spacing+$via_spacing)/2"],
    ],
    [
        ["2*$pcb_len/3", "-($ms_width+$ms_spacing+$via_spacing)/2"],
        ["2*$pcb_len/3 + $via_spacing", "-($ms_width+$ms_spacing+$via_spacing)/2"],
        ["2*$pcb_len/3 + 2*$via_spacing", "-($ms_width+$ms_spacing)/2"],
        ["$pcb_len", "-($ms_width + $ms_spacing)/2"],
    ],
]

Add traces to the EDB.

[16]:
trace_p = []
trace_n = []
for n in range(len(points_p)):
    trace_p.append(edb.modeler.create_trace(points_p[n], route_layer[n], width[n], net_p, "Flat", "Flat"))
    trace_n.append(edb.modeler.create_trace(points_n[n], route_layer[n], width[n], net_n, "Flat", "Flat"))

Create the wave ports

[17]:
edb.hfss.create_differential_wave_port(
    trace_p[0].id,
    ["0.0", "($ms_width+$ms_spacing)/2"],
    trace_n[0].id,
    ["0.0", "-($ms_width+$ms_spacing)/2"],
    "wave_port_1",
)
edb.hfss.create_differential_wave_port(
    trace_p[2].id,
    ["$pcb_len", "($ms_width+$ms_spacing)/2"],
    trace_n[2].id,
    ["$pcb_len", "-($ms_width + $ms_spacing)/2"],
    "wave_port_2",
)
[17]:
('wave_port_2',
 <pyedb.dotnet.edb_core.edb_data.ports.BundleWavePort at 0x1fa93fff9c0>)

Draw a conducting rectangle on the the ground layers.

[18]:
gnd_poly = [
    [0.0, "-$pcb_w/2"],
    ["$pcb_len", "-$pcb_w/2"],
    ["$pcb_len", "$pcb_w/2"],
    [0.0, "$pcb_w/2"],
]
gnd_shape = edb.modeler.Shape("polygon", points=gnd_poly)

Void in ground for traces on the signal routing layer

[19]:
void_poly = [
    ["$pcb_len/3", "-($ms_width+$ms_spacing+$via_spacing+$anti_pad_diam)/2-$via_spacing/2"],
    [
        "$pcb_len/3 + $via_spacing",
        "-($ms_width+$ms_spacing+$via_spacing+$anti_pad_diam)/2-$via_spacing/2",
    ],
    ["$pcb_len/3 + 2*$via_spacing", "-($ms_width+$ms_spacing+$via_spacing+$anti_pad_diam)/2"],
    ["2*$pcb_len/3 - 2*$via_spacing", "-($ms_width+$ms_spacing+$via_spacing+$anti_pad_diam)/2"],
    [
        "2*$pcb_len/3 - $via_spacing",
        "-($ms_width+$ms_spacing+$via_spacing+$anti_pad_diam)/2-$via_spacing/2",
    ],
    ["2*$pcb_len/3", "-($ms_width+$ms_spacing+$via_spacing+$anti_pad_diam)/2-$via_spacing/2"],
    ["2*$pcb_len/3", "($ms_width+$ms_spacing+$via_spacing+$anti_pad_diam)/2+$via_spacing/2"],
    [
        "2*$pcb_len/3 - $via_spacing",
        "($ms_width+$ms_spacing+$via_spacing+$anti_pad_diam)/2+$via_spacing/2",
    ],
    ["2*$pcb_len/3 - 2*$via_spacing", "($ms_width+$ms_spacing+$via_spacing+$anti_pad_diam)/2"],
    ["$pcb_len/3 + 2*$via_spacing", "($ms_width+$ms_spacing+$via_spacing+$anti_pad_diam)/2"],
    [
        "$pcb_len/3 + $via_spacing",
        "($ms_width+$ms_spacing+$via_spacing+$anti_pad_diam)/2+$via_spacing/2",
    ],
    ["$pcb_len/3", "($ms_width+$ms_spacing+$via_spacing+$anti_pad_diam)/2+$via_spacing/2"],
    ["$pcb_len/3", "($ms_width+$ms_spacing+$via_spacing+$anti_pad_diam)/2"],
]

void_shape = edb.modeler.Shape("polygon", points=void_poly)

Add ground conductors.

[20]:
for layer in layers[:-1:2]:
    # add void if the layer is the signal routing layer.
    void = [void_shape] if layer["name"] == route_layer[1] else []

    edb.modeler.create_polygon(main_shape=gnd_shape, layer_name=layer["name"], voids=void, net_name="gnd")

Plot the layout.

[21]:
edb.nets.plot(None)
PyAEDT INFO: Nets Point Generation time 0.063 seconds
C:\actions-runner\_work\pyedb\pyedb\.venv\lib\site-packages\pyedb\generic\plot.py:144: UserWarning: FigureCanvasAgg is non-interactive, and thus cannot be shown
  plt.show()

Save the EDB.

[22]:
edb.save_edb()
edb.close_edb()
PyAEDT INFO: EDB file save time: 0.00ms
PyAEDT INFO: EDB file release time: 0.00ms
[22]:
True

Open the project in HFSS 3D Layout.

[23]:
h3d = pyaedt.Hfss3dLayout(
    projectname=aedb_path,
    specified_version="2024.1",
    non_graphical=non_graphical,
    new_desktop_session=True,
)
PyAEDT WARNING: Argument `projectname` is deprecated for method `__init__`; use `project` instead.
PyAEDT WARNING: Argument `specified_version` is deprecated for method `__init__`; use `version` instead.
PyAEDT WARNING: Argument `new_desktop_session` is deprecated for method `__init__`; use `new_desktop` instead.
PyAEDT INFO: Initializing new Desktop session.
PyAEDT INFO: StdOut is enabled
PyAEDT INFO: Log on file is enabled
PyAEDT INFO: Log on Desktop Message Manager is enabled
PyAEDT INFO: Debug logger is disabled. PyAEDT methods will not be logged.
PyAEDT INFO: Launching PyAEDT outside AEDT with gRPC plugin.
PyAEDT INFO: New AEDT session is starting on gRPC port 64142
PyAEDT INFO: AEDT installation Path C:\Program Files\AnsysEM\v241\Win64
PyAEDT INFO: Ansoft.ElectronicsDesktop.2024.1 version started with process ID 3556.
PyAEDT INFO: pyaedt v0.9.9
PyAEDT INFO: Python version 3.10.11 (tags/v3.10.11:7d4cc5a, Apr  5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)]
PyAEDT INFO: AEDT 2024.1.0 Build Date 2023-11-27 22:16:18
PyAEDT INFO: EDB folder C:\Users\ansys\AppData\Local\Temp\tmp87_os07x.ansys\pcb.aedb has been imported to project pcb
PyAEDT INFO: Active Design set to Cell_1AZRX5
PyAEDT INFO: Aedt Objects correctly read

Add HFSS simulation setup#

Add HFSS simulation setup.

[24]:
setup = h3d.create_setup()
setup.props["AdaptiveSettings"]["SingleFrequencyDataList"]["AdaptiveFrequencyData"]["MaxPasses"] = 3

h3d.create_linear_count_sweep(
    setupname=setup.name,
    unit="GHz",
    freqstart=0,
    freqstop=10,
    num_of_freq_points=1001,
    sweepname="sweep1",
    sweep_type="Interpolating",
    interpolation_tol_percent=1,
    interpolation_max_solutions=255,
    save_fields=False,
    use_q3d_for_dc=False,
)
PyAEDT WARNING: Argument `setupname` is deprecated for method `create_linear_count_sweep`; use `setup` instead.
PyAEDT WARNING: Argument `freqstart` is deprecated for method `create_linear_count_sweep`; use `start_frequency` instead.
PyAEDT WARNING: Argument `freqstop` is deprecated for method `create_linear_count_sweep`; use `stop_frequency` instead.
PyAEDT WARNING: Argument `sweepname` is deprecated for method `create_linear_count_sweep`; use `name` instead.
PyAEDT INFO: Linear count sweep sweep1 has been correctly created.
[24]:
<pyaedt.modules.SolveSweeps.SweepHFSS3DLayout at 0x1fa98595ce0>

Define the differential pairs to used to calculate differential and common mode s-parameters.

[25]:
h3d.set_differential_pair(diff_name="In", positive_terminal="wave_port_1:T1", negative_terminal="wave_port_1:T2")
h3d.set_differential_pair(diff_name="Out", positive_terminal="wave_port_2:T1", negative_terminal="wave_port_2:T2")
PyAEDT WARNING: Argument `positive_terminal` is deprecated for method `set_differential_pair`; use `assignment` instead.
PyAEDT WARNING: Argument `negative_terminal` is deprecated for method `set_differential_pair`; use `reference` instead.
PyAEDT WARNING: Argument `diff_name` is deprecated for method `set_differential_pair`; use `differential_mode` instead.
PyAEDT WARNING: Argument `positive_terminal` is deprecated for method `set_differential_pair`; use `assignment` instead.
PyAEDT WARNING: Argument `negative_terminal` is deprecated for method `set_differential_pair`; use `reference` instead.
PyAEDT WARNING: Argument `diff_name` is deprecated for method `set_differential_pair`; use `differential_mode` instead.
[25]:
True

Solve the project.

[26]:
h3d.analyze()
PyAEDT INFO: Key Desktop/ActiveDSOConfigurations/HFSS 3D Layout Design correctly changed.
PyAEDT INFO: Solving all design setups.
PyAEDT INFO: Key Desktop/ActiveDSOConfigurations/HFSS 3D Layout Design correctly changed.
PyAEDT INFO: Design setup None solved correctly in 0.0h 1.0m 26.0s
[26]:
True

Plot the results and shut down AEDT.

[27]:
solutions = h3d.post.get_solution_data(["dB(S(In,In))", "dB(S(In,Out))"], context="Differential Pairs")
solutions.plot()
h3d.release_desktop()
PyAEDT INFO: Parsing C:/Users/ansys/AppData/Local/Temp/tmp87_os07x.ansys/pcb.aedt.
PyAEDT INFO: File C:/Users/ansys/AppData/Local/Temp/tmp87_os07x.ansys/pcb.aedt correctly loaded. Elapsed time: 0m 0sec
PyAEDT INFO: aedt file load time 0.03124833106994629
PyAEDT INFO: Loading Modeler.
PyAEDT INFO: Modeler loaded.
PyAEDT INFO: EDB loaded.
PyAEDT INFO: Layers loaded.
PyAEDT INFO: Primitives loaded.
PyAEDT INFO: Modeler class has been initialized! Elapsed time: 0m 0sec
PyAEDT INFO: PostProcessor class has been initialized! Elapsed time: 0m 0sec
PyAEDT INFO: Post class has been initialized! Elapsed time: 0m 0sec
C:\actions-runner\_work\pyedb\pyedb\.venv\lib\site-packages\pyaedt\generic\plot.py:55: UserWarning: The PyVista module is required to run some functionalities of PostProcess.
Install with

pip install pyvista

Requires CPython.
  warnings.warn(
PyAEDT INFO: Solution Data Correctly Loaded.
PyAEDT INFO: Desktop has been released and closed.
[27]:
True

Note that the ground nets are only connected to each other due to the wave ports. The problem with poor grounding can be seen in the S-parameters. This example can be downloaded as a Jupyter Notebook, so you can modify it. Try changing parameters or adding ground vias to improve performance.

The final cell cleans up the temporary directory, removing all files.

[28]:
temp_dir.cleanup()