pyEDB differential wave port issue in 25R1 vs 24R1

a_austin
a_austin Member Posts: 12
10 Comments Name Dropper
**

One of our customers reports an issue with creating a differential wave port using pyEDB in 25R1 compared to 24R1. In 24R1, no errors on the wave port definition occurs:

In 25R1, we get an error on the definition:

I've an example script from them that demonstrates the issue. They were able to work around it by adjusting the API, but wanted to know if this is a bug.

Tagged:

Answers

  • James Derrick
    James Derrick Administrator, Employee Posts: 357
    Ancient Membership 100 Comments 100 Likes 25 Answers
    admin

    Hi @a_austin can you share the script that demonstrates the issue?

  • a_austin
    a_austin Member Posts: 12
    10 Comments Name Dropper
    **

    @James Derrick Sure thing. I've attached it here.

  • James Derrick
    James Derrick Administrator, Employee Posts: 357
    Ancient Membership 100 Comments 100 Likes 25 Answers
    admin

    Thanks! Although, unfortunately for security reasons I can't download that. Can you share the code in a comment as a snippet?

  • a_austin
    a_austin Member Posts: 12
    10 Comments Name Dropper
    **
    edited May 29

    What is the 'attach file' option for then?

    I've gotta break it up into chunks then...

    ================= chunk 1
    
    # # 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
    #
    # <img src="_static\pre_layout_parameterized_pcb.png" width="500">
    
    # ## Preparation
    # Import the required packages
    
    # +
    import os
    import tempfile
    import time
    
    from ansys.aedt.core import Hfss3dLayout
    from pyedb import Edb
    # -
    
    # ## Define constants
    
    AEDT_VERSION = "2025.1"
    NUM_CORES = 4
    NG_MODE = False  # Open AEDT UI when it is launched.
    
    # ## Launch EDB
    
    temp_folder = tempfile.TemporaryDirectory(suffix=".ansys")
    aedb_path = os.path.join(temp_folder.name, "pcb.aedb")
    edb = Edb(edbpath=aedb_path, edbversion=AEDT_VERSION)
    
    # ## Create layout
    # ### Define the parameters.
    
    # +
    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.
    
    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"},
    ]
    
  • a_austin
    a_austin Member Posts: 12
    10 Comments Name Dropper
    **
    edited May 29
    ==================== chunk 2
    
    # Define the bottom layer
    
    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.
    
    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"],
    )
    
    # Assign net names. There are only two signal nets.
    
    net_p = "p"
    net_n = "n"
    
    # Place the signal vias.
    
    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,
    )
    
    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,
    )
    
    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,
    )
    
    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,
    )
    
    
    # ### Draw parametrized traces
    #
    # Trace width and the routing (Microstrip-Stripline-Microstrip).
    # Applies to both p and n nets.
    
    # 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
    
    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"],
        ],
    ]
    
  • a_austin
    a_austin Member Posts: 12
    10 Comments Name Dropper
    **
    edited May 29
    =========== chunk 3
    
    # Define points for three traces in the "n" net
    
    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.
    
    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"
            )
        )
    placehere = trace_p[0].get_closest_point(["0.0", "($ms_width+$ms_spacing)/2"])
    placehere2 = trace_n[0].get_closest_point(["0.0", "-($ms_width+$ms_spacing)/2"])
    
    #
    # Create the wave ports by creating two single-ended waveports then coupling
    # them together.
    #
    name1, wave_port1 = edb.hfss.create_wave_port(
        trace_p[0].id,
        placehere,
    )
    name2, wave_port2 = edb.hfss.create_wave_port(
        trace_n[0].id,
        placehere2,
    )
    wave_port1.couple_ports(wave_port2)
    
    #
    # The original call for creating the first differential wave port is commented
    # out below:
    #
    #diff1 = edb.hfss.create_differential_wave_port(
    #    trace_p[0].id,
    #    placehere,
    #    trace_n[0].id,
    #    placehere2,
    #    "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",
    )
    
    # Draw a conducting rectangle on the the ground layers.
    
    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
    
    # +
    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)
    # -
    
  • a_austin
    a_austin Member Posts: 12
    10 Comments Name Dropper
    **
    edited May 29
    ============== chunk 4
    
    # Add ground conductors.
    
    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.
    
    edb.nets.plot(None)
    
    # Save the EDB.
    
    edb.save_edb()
    edb.close_edb()
    
    # ## Open the project in HFSS 3D Layout.
    
    h3d = Hfss3dLayout(
        project=aedb_path,
        version=AEDT_VERSION,
        non_graphical=NG_MODE,
        new_desktop=True,
    )
    
    # ### Add a HFSS simulation setup
    
    # +
    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,
    )
    # -
    
    # ### Define the differential pairs to used to calculate differential and common mode  s-parameters
    
    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"
    )
    
    # Solve the project.
    
    h3d.analyze(cores=NUM_CORES)
    
    # Plot the results and shut down AEDT.
    
    solutions = h3d.post.get_solution_data(
        expressions=["dB(S(In,In))", "dB(S(In,Out))"], context="Differential Pairs"
    )
    #solutions.plot()
    
    # Wait 120 seconds to give us time to view any error messages
    time.sleep(120)
    
    # ## Release AEDT
    
    h3d.save_project()
    h3d.release_desktop()
    # Wait 3 seconds to allow AEDT to shut down before cleaning the temporary directory.
    time.sleep(3)
    
    # 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.
    
    temp_folder.cleanup()
    
  • a_austin
    a_austin Member Posts: 12
    10 Comments Name Dropper
    **

    This is the chunk that is relevant...

    # Add traces to the EDB.
    
    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"
            )
        )
    placehere = trace_p[0].get_closest_point(["0.0", "($ms_width+$ms_spacing)/2"])
    placehere2 = trace_n[0].get_closest_point(["0.0", "-($ms_width+$ms_spacing)/2"])
    
    #
    # Create the wave ports by creating two single-ended waveports then coupling
    # them together.
    #
    name1, wave_port1 = edb.hfss.create_wave_port(
        trace_p[0].id,
        placehere,
    )
    name2, wave_port2 = edb.hfss.create_wave_port(
        trace_n[0].id,
        placehere2,
    )
    wave_port1.couple_ports(wave_port2)
    
    #
    # The original call for creating the first differential wave port is commented
    # out below:
    #
    #diff1 = edb.hfss.create_differential_wave_port(
    #    trace_p[0].id,
    #    placehere,
    #    trace_n[0].id,
    #    placehere2,
    #    "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",
    )
    
  • James Derrick
    James Derrick Administrator, Employee Posts: 357
    Ancient Membership 100 Comments 100 Likes 25 Answers
    admin

    Hi @a_austin whilst we at Ansys can't download attached files, other members of the forum may still prefer to and we don't prohibit that, this is just an Ansys policy. :)

    I have gone through and added the correct formatting for your code blocks. Additionally, thank you for including the relevant part as well.

  • a_austin
    a_austin Member Posts: 12
    10 Comments Name Dropper
    **

    @James Derrick said:
    Hi @a_austin whilst we at Ansys can't download attached files, other members of the forum may still prefer to and we don't prohibit that, this is just an Ansys policy. :)

    I have gone through and added the correct formatting for your code blocks. Additionally, thank you for including the relevant part as well.

    Yes, makes sense. Let me know if you need anything else.

  • svandenb-dev
    svandenb-dev Member, Employee Posts: 9
    Second Anniversary Ansys Employee First Answer Name Dropper
    ✭✭✭

    Hi @a_austin,
    If you confirm that taking the same EDB imported in aedt works fine with 241 but fails on 251 that's definitely a bug which should be submitted. If you are allowed to share the edb during the bug submission would be the best, please don't mention pyedb in that case. If you think this is bug related to pyedb, please join us on github:
    https://github.com/ansys/pyedb
    This is the best way to get support and exchange information with pyedb development team.

  • a_austin
    a_austin Member Posts: 12
    10 Comments Name Dropper
    **

    @svandenb-dev said:
    Hi @a_austin,
    If you confirm that taking the same EDB imported in aedt works fine with 241 but fails on 251 that's definitely a bug which should be submitted. If you are allowed to share the edb during the bug submission would be the best, please don't mention pyedb in that case. If you think this is bug related to pyedb, please join us on github:
    https://github.com/ansys/pyedb
    This is the best way to get support and exchange information with pyedb development team.

    The code creates an EDB from scratch... works fine in 24R1 and fails in 25R1.

  • a_austin
    a_austin Member Posts: 12
    10 Comments Name Dropper
    **

    I posted on GitHub yesterday. https://github.com/ansys/pyedb/issues/1276