Download this example

Download this example as a Jupyter Notebook or as a Python script.


Pre-layout Parameterized PCB#

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

86aed4bfeb8c4cba9fca0c2531088107

Preparation#

Import the required packages

[1]:
import os
import tempfile
import time

from ansys.aedt.core import Hfss3dLayout
from pyedb import Edb

Define constants#

[2]:
AEDT_VERSION = "2024.2"
NUM_CORES = 4
NG_MODE = False  # Open AEDT UI when it is launched.

Launch EDB#

[3]:
temp_folder = tempfile.TemporaryDirectory(suffix=".ansys")
aedb_path = os.path.join(temp_folder.name, "pcb.aedb")
edb = Edb(edbpath=aedb_path, edbversion=AEDT_VERSION)
PyAEDT INFO: Logger is initialized in EDB.
PyAEDT INFO: legacy v0.31.0
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\tmp54rhlyzi.ansys\pcb.aedb created correctly.
PyAEDT INFO: EDB initialized.

Create layout#

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])

Create stackup#

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"},
]

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"]

Create a parametrized padstack for the signal via.#

Create a padstack definition.

[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 0x1d44e59b190>
[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 0x1d44e59bd60>
[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 0x1d44e59b400>
[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 0x1d44e59be20>

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 0x1d44e4d1b70>)

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.033 seconds
../../../../_images/examples_high_frequency_layout_signal_integrity_pre_layout_parametrized_39_1.png
[21]:
<module 'matplotlib.pyplot' from 'C:\\actions-runner\\_work\\pyaedt-examples\\pyaedt-examples\\.venv\\lib\\site-packages\\matplotlib\\pyplot.py'>

Save the EDB.

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

Open the project in HFSS 3D Layout.#

[23]:
h3d = Hfss3dLayout(
    project=aedb_path,
    version=AEDT_VERSION,
    non_graphical=NG_MODE,
    new_desktop=True,
)
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: PyAEDT version 0.12.dev0.
PyAEDT INFO: Initializing new Desktop session.
PyAEDT INFO: Log on console is enabled.
PyAEDT INFO: Log on file C:\Users\ansys\AppData\Local\Temp\pyaedt_ansys_d9da9710-dc24-4c5b-82c4-db8cb220f120.log is enabled.
PyAEDT INFO: Log on AEDT is enabled.
PyAEDT INFO: Debug logger is disabled. PyAEDT methods will not be logged.
PyAEDT INFO: Launching PyAEDT with gRPC plugin.
PyAEDT INFO: New AEDT session is starting on gRPC port 63307
PyAEDT INFO: AEDT installation Path C:\Program Files\AnsysEM\v242\Win64
PyAEDT INFO: Ansoft.ElectronicsDesktop.2024.2 version started with process ID 4348.
PyAEDT INFO: EDB folder C:\Users\ansys\AppData\Local\Temp\tmp54rhlyzi.ansys\pcb.aedb has been imported to project pcb
PyAEDT INFO: Active Design set to 0;Cell_6XLN1H
PyAEDT INFO: Aedt Objects correctly read

Add a HFSS simulation setup#

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

h3d.create_linear_count_sweep(
    setup=setup.name,
    unit="GHz",
    start_frequency=0,
    stop_frequency=10,
    num_of_freq_points=1001,
    name="sweep1",
    sweep_type="Interpolating",
    interpolation_tol_percent=1,
    interpolation_max_solutions=255,
    save_fields=False,
    use_q3d_for_dc=False,
)
PyAEDT INFO: Linear count sweep sweep1 has been correctly created.
[24]:
<ansys.aedt.core.modules.solve_sweeps.SweepHFSS3DLayout at 0x1d455147af0>

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

[25]:
h3d.set_differential_pair(
    differential_mode="In", assignment="wave_port_1:T1", reference="wave_port_1:T2"
)
h3d.set_differential_pair(
    differential_mode="Out", assignment="wave_port_2:T1", reference="wave_port_2:T2"
)
[25]:
True

Solve the project.

[26]:
h3d.analyze(cores=NUM_CORES)
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 34.0s
[26]:
True

Plot the results and shut down AEDT.

[27]:
solutions = h3d.post.get_solution_data(
    expressions=["dB(S(In,In))", "dB(S(In,Out))"], context="Differential Pairs"
)
solutions.plot()
PyAEDT INFO: Parsing C:/Users/ansys/AppData/Local/Temp/tmp54rhlyzi.ansys/pcb.aedt.
PyAEDT INFO: File C:/Users/ansys/AppData/Local/Temp/tmp54rhlyzi.ansys/pcb.aedt correctly loaded. Elapsed time: 0m 0sec
PyAEDT INFO: aedt file load time 0.015619754791259766
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
PyAEDT INFO: Solution Data Correctly Loaded.
[27]:
../../../../_images/examples_high_frequency_layout_signal_integrity_pre_layout_parametrized_51_1.png
../../../../_images/examples_high_frequency_layout_signal_integrity_pre_layout_parametrized_51_2.png

Release AEDT#

[28]:
h3d.save_project()
h3d.release_desktop()
# Wait 3 seconds to allow AEDT to shut down before cleaning the temporary directory.
time.sleep(3)
PyAEDT INFO: Project pcb Saved correctly
PyAEDT INFO: Desktop has been released and closed.

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.

[29]:
temp_folder.cleanup()

Download this example

Download this example as a Jupyter Notebook or as a Python script.