diff --git a/doc/make.bat b/doc/make.bat index a0b34980b..9a3a99f10 100644 --- a/doc/make.bat +++ b/doc/make.bat @@ -41,6 +41,7 @@ goto end :html %SPHINXBUILD% -M html %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% -v %O% +goto end :pdf %SPHINXBUILD% -M latex %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% diff --git a/doc/source/conf.py b/doc/source/conf.py index 0075cf4be..2d868341b 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -195,7 +195,12 @@ def check_example_error(app, pagename, templatename, context, doctree): """ # Check if the HTML contains an error message if pagename.startswith("examples") and not pagename.endswith("/index"): - if any(map(lambda msg: msg in context["body"], ["UsageError", "NameError"])): + if any( + map( + lambda msg: msg in context["body"], + ["UsageError", "NameError", "DeadKernelError", "NotebookError"], + ) + ): logger.error(f"An error was detected in file {pagename}") app.builder.config.html_context["build_error"] = True diff --git a/examples/00-EDB/01_edb_example.py b/examples/00-EDB/01_edb_example.py new file mode 100644 index 000000000..9384fd3c2 --- /dev/null +++ b/examples/00-EDB/01_edb_example.py @@ -0,0 +1,207 @@ +# # EDB: SIwave DC-IR Analysis +# +# This example demonstrates the use of EDB to interact with a PCB +# layout and run DC-IR analysis in SIwave. +# Perform required imports + +# + +import os +import tempfile +import time + +from ansys.pyaedt.examples.constants import EDB_VERSION +import pyaedt + +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +targetfile = pyaedt.downloads.download_file("edb/ANSYS-HSD_V1.aedb", destination=temp_dir.name) + +siwave_file = os.path.join(os.path.dirname(targetfile), "ANSYS-HSD_V1.siw") +print(targetfile) +aedt_file = targetfile[:-4] + "aedt" +# - + +# ## Launch Ansys Electronics Database (EDB) +# +# Instantiate an instance of the `pyaedt.Edb` class using SI units. + +# + +if os.path.exists(aedt_file): + os.remove(aedt_file) + +# Select EDB version (change it manually if needed, e.g. "2023.2") +edb_version = EDB_VERSION +print(f"EDB version: {edb_version}") + +edb = pyaedt.Edb(edbpath=targetfile, edbversion=edb_version) +# - + +# ## Identify nets and components +# +# The ``Edb.nets.netlist`` and ``Edb.components.components`` properties contain information +# about all of the nets and components. The following cell uses this information to print the +# number of nets and components. + +print("Nets {}".format(len(edb.nets.netlist))) +start = time.time() +print("Components {}".format(len(edb.components.components.keys()))) +print("elapsed time = ", time.time() - start) + +# ## Identify pin positions +# +# This code shows how to obtain all pins for a specific component and +# print the ``[x, y]`` position of each pin. + +pins = edb.components["U2"].pins +count = 0 +for pin in edb.components["U2"].pins.values(): + if count < 10: # Only print the first 10 pin coordinates. + print(pin.position) + elif count == 10: + print("...and many more.") + else: + pass + count += 1 + +# Get all nets connected to a specific component. Print +# the pin and the name of the net that it is connected to. + +connections = edb.components.get_component_net_connection_info("U2") +n_print = 0 # Counter to limit the number of printed lines. +print_max = 15 +for m in range(len(connections["pin_name"])): + ref_des = connections["refdes"][m] + pin_name = connections["pin_name"][m] + net_name = connections["net_name"][m] + if net_name != "" and (n_print < print_max): + print('{}, pin {} -> net "{}"'.format(ref_des, pin_name, net_name)) + n_print += 1 + elif n_print == print_max: + print("...and many more.") + n_print += 1 + +# Compute rats. + +rats = edb.components.get_rats() + +# ## Identify connected nets +# +# The ``get_dcconnected_net_list()`` method retrieves a list of +# all DC-connected power nets. Each group of connected nets is returned +# as a [set](https://docs.python.org/3/tutorial/datastructures.html#sets). +# The first argument to the method is the list of ground nets, which are +# not considered in the search for connected nets. + +GROUND_NETS = ["GND", "GND_DP"] +dc_connected_net_list = edb.nets.get_dcconnected_net_list(GROUND_NETS) +for pnets in dc_connected_net_list: + print(pnets) + +# ## Power Tree +# +# The power tree provides connectivity through all components from the VRM to +# the device. + +VRM = "U1" +OUTPUT_NET = "AVCC_1V3" +powertree_df, component_list_columns, net_group = edb.nets.get_powertree(OUTPUT_NET, GROUND_NETS) + +# Print some information about the power tree. + +print_columns = ["refdes", "pin_name", "component_partname"] +ncol = [component_list_columns.index(c) for c in print_columns] + +# This prints the header. Replace "pin_name" with "pin" to +# make the header align with the values. + +# + +print("\t".join(print_columns).replace("pin_name", "pin")) + +for el in powertree_df: + s = "" + count = 0 + for e in el: + if count in ncol: + s += "{}\t".format(e) + count += 1 + s.rstrip() + print(s) +# - + +# ## Remove unused components +# +# Delete all RLC components that are connected with only one pin. +# The ``Edb.components.delete_single_pin_rlc()`` method +# provides a useful way to +# remove components that are not needed for the simulation. + +edb.components.delete_single_pin_rlc() + +# You can also remove unused components explicitly by name. + +edb.components.delete("C380") + +# Nets can also be removed explicitly. + +edb.nets.delete("PDEN") + +# Print the top and bottom elevation of the stackup obtained using +# the ``Edb.stackup.limits()`` method. + +s = 'Top layer name: "{top}", Elevation: {top_el:.2f} ' +s += 'mm\nBottom layer name: "{bot}", Elevation: {bot_el:2f} mm' +top, top_el, bot, bot_el = edb.stackup.limits() +print(s.format(top=top, top_el=top_el * 1e3, bot=bot, bot_el=bot_el * 1e3)) + +# ## Set up for SIwave DCIR analysis +# +# Create a voltage source and then set up a DCIR analysis. + +edb.siwave.create_voltage_source_on_net("U1", "AVCC_1V3", "U1", "GND", 1.3, 0, "V1") +edb.siwave.create_current_source_on_net("IC2", "NetD3_2", "IC2", "GND", 1.0, 0, "I1") +setup = edb.siwave.add_siwave_dc_analysis("myDCIR_4") +setup.use_dc_custom_settings = True +setup.set_dc_slider = 0 +setup.add_source_terminal_to_ground("V1", 1) + +# ## Solve +# +# Save the modifications and run the analysis in SIwave. + +edb.save_edb() +edb.nets.plot(None, "1_Top", plot_components_on_top=True) + +siw_file = edb.solve_siwave() + +# ## Export results +# +# Export all quantities calculated from the DC-IR analysis. +# The following method runs SIwave in batch mode from the command line. +# Results are written to the edb folder. + +outputs = edb.export_siwave_dc_results( + siw_file, + setup.name, +) + +# Close EDB. After EDB is closed, it can be opened by AEDT. + +edb.close_edb() + +# ## View Layout in SIwave +# +# The SIwave user interface can be visualized and manipulated +# using the SIwave user interface. This command works on Window OS only. + +# + +# siwave = pyaedt.Siwave("2023.2") +# siwave.open_project(siwave_file) +# report_file = os.path.join(temp_folder,'Ansys.htm') + +# siwave.export_siwave_report("myDCIR_4", report_file) +# siwave.close_project() +# siwave.quit_application() +# - + +# Clean up the temporary files and directory. + +temp_dir.cleanup() diff --git a/examples/00-EDB/02_edb_to_ipc2581.py b/examples/00-EDB/02_edb_to_ipc2581.py new file mode 100644 index 000000000..0dace03b1 --- /dev/null +++ b/examples/00-EDB/02_edb_to_ipc2581.py @@ -0,0 +1,73 @@ +# # EDB: IPC2581 export +# +# This example shows how you can use PyAEDT to export an IPC2581 file. +# +# Perform required imports, which includes importing a section. + +# + +import os +import tempfile + +from ansys.pyaedt.examples.constants import EDB_VERSION +import pyaedt + +# - + +# ## Download the AEDB file and copy it in the temporary folder. + +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +targetfile = pyaedt.downloads.download_file("edb/ANSYS-HSD_V1.aedb", destination=temp_dir.name) +ipc2581_file_name = os.path.join(temp_dir.name, "Ansys_Hsd.xml") +print(targetfile) + +# ## Launch EDB +# +# Launch the `pyaedt.Edb` class, using EDB 2023. +# > Note that length dimensions passed to EDB are in SI units. + +# + +# Select EDB version (change it manually if needed, e.g. "2023.2") +edb_version = EDB_VERSION +print(f"EDB version: {edb_version}") + +edb = pyaedt.Edb(edbpath=targetfile, edbversion=edb_version) +# - + +# ## Parametrize the width of a trace. + +edb.modeler.parametrize_trace_width( + "A0_N", parameter_name=pyaedt.generate_unique_name("Par"), variable_value="0.4321mm" +) + +# ## Create a cutout and plot it. + +signal_list = [] +for net in edb.nets.netlist: + if "PCIe" in net: + signal_list.append(net) +power_list = ["GND"] +edb.cutout( + signal_list=signal_list, + reference_list=power_list, + extent_type="ConvexHull", + expansion_size=0.002, + use_round_corner=False, + number_of_threads=4, + remove_single_pin_components=True, + use_pyaedt_extent_computing=True, + extent_defeature=0, +) +edb.nets.plot(None, None, color_by_net=True) + +# ## Export the EDB to an IPC2581 file. + +edb.export_to_ipc2581(ipc2581_file_name, "inch") +print("IPC2581 File has been saved to {}".format(ipc2581_file_name)) + +# ## Close EDB + +edb.close_edb() + +# ## Clean up the temporary directory + +temp_dir.cleanup() diff --git a/examples/00-EDB/03_5G_antenna_example_parametrics.py b/examples/00-EDB/03_5G_antenna_example_parametrics.py new file mode 100644 index 000000000..16e97e25b --- /dev/null +++ b/examples/00-EDB/03_5G_antenna_example_parametrics.py @@ -0,0 +1,384 @@ +# # EDB: Layout Components +# +# This example shows how you can use EDB to create a parametric component using +# 3D Layout and use it in HFSS 3D. + +# ## Perform required imports +# +# Perform required imports, which includes importing the ``Hfss3dlayout`` object +# and initializing it on version 2023 R2. + +# + +import os +import tempfile + +from ansys.pyaedt.examples.constants import EDB_VERSION +import pyaedt + +# - + +# ## Set non-graphical mode + +non_graphical = False + +# ## Create data classes +# +# Data classes are useful to do calculations and store variables. +# There are three data classes: ``Patch``, ``Line``, and ``Array``. + +# + +class Patch: + def __init__(self, width=0.0, height=0.0, position=0.0): + self.width = width + self.height = height + self.position = position + + @property + def points(self): + return [ + [self.position, "-{}/2".format(self.height)], + ["{} + {}".format(self.position, self.width), "-{}/2".format(self.height)], + ["{} + {}".format(self.position, self.width), "{}/2".format(self.height)], + [self.position, "{}/2".format(self.height)], + ] + + +class Line: + def __init__(self, length=0.0, width=0.0, position=0.0): + self.length = length + self.width = width + self.position = position + + @property + def points(self): + return [ + [self.position, "-{}/2".format(self.width)], + ["{} + {}".format(self.position, self.length), "-{}/2".format(self.width)], + ["{} + {}".format(self.position, self.length), "{}/2".format(self.width)], + [self.position, "{}/2".format(self.width)], + ] + + +class LinearArray: + def __init__(self, nb_patch=1, array_length=10e-3, array_width=5e-3): + self.nbpatch = nb_patch + self.length = array_length + self.width = array_width + + @property + def points(self): + return [ + [-1e-3, "-{}/2-1e-3".format(self.width)], + ["{}+1e-3".format(self.length), "-{}/2-1e-3".format(self.width)], + ["{}+1e-3".format(self.length), "{}/2+1e-3".format(self.width)], + [-1e-3, "{}/2+1e-3".format(self.width)], + ] + + +# - + +# ## Launch EDB +# +# PyAEDT.Edb allows to open existing Edb project or create a new empty project. + +# + +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +aedb_path = os.path.join(temp_dir.name, "linear_array.aedb") + +# Select EDB version (change it manually if needed, e.g. "2023.2") +edb_version = EDB_VERSION +print(f"EDB version: {edb_version}") + +# Create an instance of the Edb class. +edb = pyaedt.Edb(edbpath=aedb_path, edbversion=edb_version) +# - + +# Add stackup layers + +edb.stackup.add_layer("Virt_GND") +edb.stackup.add_layer( + "Gap", "Virt_GND", layer_type="dielectric", thickness="0.05mm", material="Air" +) +edb.stackup.add_layer("GND", "Gap") +edb.stackup.add_layer( + "Substrat", "GND", layer_type="dielectric", thickness="0.5mm", material="Duroid (tm)" +) +edb.stackup.add_layer("TOP", "Substrat") + +# Create the the first patch and feed line using the ``Patch``, ``Line``classes defined above. +# +# Define parameters: + +# + +edb["w1"] = 1.4e-3 +edb["h1"] = 1.2e-3 +edb["initial_position"] = 0.0 +edb["l1"] = 2.4e-3 +edb["trace_w"] = 0.3e-3 + +first_patch = Patch(width="w1", height="h1", position="initial_position") +edb.modeler.create_polygon(first_patch.points, "TOP", net_name="Array_antenna") +# - + +# First line + +first_line = Line(length="l1", width="trace_w", position=first_patch.width) +edb.modeler.create_polygon(first_line.points, "TOP", net_name="Array_antenna") + +# Now use the ``LinearArray`` class to create the array. + +# + +edb["w2"] = 2.29e-3 +edb["h2"] = 3.3e-3 +edb["l2"] = 1.9e-3 +edb["trace_w2"] = 0.2e-3 + +patch = Patch(width="w2", height="h2") +line = Line(length="l2", width="trace_w2") +linear_array = LinearArray(nb_patch=8, array_width=patch.height) + +current_patch = 1 +current_position = "{} + {}".format(first_line.position, first_line.length) + +while current_patch <= linear_array.nbpatch: + patch.position = current_position + edb.modeler.create_polygon(patch.points, "TOP", net_name="Array_antenna") + current_position = "{} + {}".format(current_position, patch.width) + if current_patch < linear_array.nbpatch: + line.position = current_position + edb.modeler.create_polygon(line.points, "TOP", net_name="Array_antenna") + current_position = "{} + {}".format(current_position, line.length) + current_patch += 1 + +linear_array.length = current_position +# - + +# Add the ground conductor. + +edb.modeler.create_polygon(linear_array.points, "GND", net_name="GND") + +# Add the connector pin to use to assign the port. + +edb.padstacks.create( + padstackname="Connector_pin", holediam="100um", paddiam="0", antipaddiam="200um" +) +con_pin = edb.padstacks.place( + ["{}/4.0".format(first_patch.width), 0], + "Connector_pin", + net_name="Array_antenna", + fromlayer="TOP", + tolayer="GND", + via_name="coax", +) + +# Add a connector ground. + +edb.modeler.create_polygon(first_patch.points, "Virt_GND", net_name="GND") +edb.padstacks.create("gnd_via", "100um", "0", "0") +edb["via_spacing"] = 0.2e-3 +con_ref1 = edb.padstacks.place( + [ + "{} + {}".format(first_patch.points[0][0], "via_spacing"), + "{} + {}".format(first_patch.points[0][1], "via_spacing"), + ], + "gnd_via", + fromlayer="GND", + tolayer="Virt_GND", + net_name="GND", +) +con_ref2 = edb.padstacks.place( + [ + "{} + {}".format(first_patch.points[1][0], "-via_spacing"), + "{} + {}".format(first_patch.points[1][1], "via_spacing"), + ], + "gnd_via", + fromlayer="GND", + tolayer="Virt_GND", + net_name="GND", +) +con_ref3 = edb.padstacks.place( + [ + "{} + {}".format(first_patch.points[2][0], "-via_spacing"), + "{} + {}".format(first_patch.points[2][1], "-via_spacing"), + ], + "gnd_via", + fromlayer="GND", + tolayer="Virt_GND", + net_name="GND", +) +con_ref4 = edb.padstacks.place( + [ + "{} + {}".format(first_patch.points[3][0], "via_spacing"), + "{} + {}".format(first_patch.points[3][1], "-via_spacing"), + ], + "gnd_via", + fromlayer="GND", + tolayer="Virt_GND", + net_name="GND", +) + +# Define the port. + +edb.padstacks.set_solderball(con_pin, "Virt_GND", isTopPlaced=False, ballDiam=0.1e-3) +port_name = edb.padstacks.create_coax_port(con_pin) + +# Display the model using the ``Edb.nets.plot()`` method. + +edb.nets.plot() + +# The EDB is complete. Now close the EDB and import it into HFSS as a "Layout Component". + +edb.save_edb() +edb.close_edb() +print("EDB saved correctly to {}. You can import in AEDT.".format(aedb_path)) + +# ## 3D component in HFSS +# +# First create an instance of the ``pyaedt.Hfss`` class. If you set +# > ``non_graphical = False +# +# then AEDT user interface will be visible after the following cell is executed. +# It is now possible to monitor the progress in the UI as each of the following cells is executed. +# All commands can be run without the UI by changing the value of ``non_graphical``. + +h3d = pyaedt.Hfss( + projectname="Demo_3DComp", + designname="Linear_Array", + specified_version="2023.2", + new_desktop_session=True, + non_graphical=non_graphical, + close_on_exit=True, + solution_type="Terminal", +) + +# Set units to ``mm``. + +h3d.modeler.model_units = "mm" + +# ## Import the EDB as a 3D component +# +# One or more layout components can be imported into HFSS. +# The combination of layout data and 3D CAD data helps streamline model creation and setup. + +component = h3d.modeler.insert_layout_component(aedb_path, parameter_mapping=True) + +# ## Expose the component parameters +# +# If a layout component is parametric, you can expose and change parameters in HFSS + +# + +component.parameters + +w1_name = "{}_{}".format("w1", h3d.modeler.user_defined_component_names[0]) +h3d[w1_name] = 0.0015 +# - + +# ### Radiation Boundary Assignment +# +# The 3D domain includes the air volume surrounding the antenna. +# This antenna will be simulted from 20 GHz - 50 GHz. +# +# A "radiation boundary" will be assigned to the outer boundaries of the domain. +# This boundary should be roughly one quarter wavelength away from the radiating structure: +# +# $$ \lambda/4 = \frac{c_0}{4 f} \approx 2.8mm $$ + +# + +h3d.modeler.fit_all() + +h3d.modeler.create_air_region(2.8, 2.8, 2.8, 2.8, 2.8, 2.8, is_percentage=False) +h3d.assign_radiation_boundary_to_objects("Region") +# - + +# ### Set up analysis +# +# The finite element mesh is adapted iteratively. +# The maximum number of adaptive passes is set using the ``MaximumPasses`` property. +# This model converges such that the $S_{11}$ is independent of the mesh. +# The default accuracy setting is: +# $$ \max(|\Delta S|) < 0.02 $$ + +setup = h3d.create_setup() +setup.props["Frequency"] = "20GHz" +setup.props["MaximumPasses"] = 10 + +# Specify properties of the frequency sweep: + +sweep1 = setup.add_sweep(sweepname="20GHz_to_50GHz") +sweep1.props["RangeStart"] = "20GHz" +sweep1.props["RangeEnd"] = "50GHz" +sweep1.update() + +# Solve the project + +h3d.analyze() + +# ## Plot results outside AEDT +# +# Plot results using Matplotlib. + +trace = h3d.get_traces_for_plot() +solution = h3d.post.get_solution_data(trace[0]) +solution.plot() + +# ## Plot far fields in AEDT +# +# Plot radiation patterns in AEDT. + +# + +variations = {} +variations["Freq"] = ["20GHz"] +variations["Theta"] = ["All"] +variations["Phi"] = ["All"] +h3d.insert_infinite_sphere(name="3D") + +new_report = h3d.post.reports_by_category.far_field( + "db(RealizedGainTotal)", h3d.nominal_adaptive, "3D" +) +new_report.variations = variations +new_report.primary_sweep = "Theta" +new_report.create("Realized2D") +# - + +# ## Plot far fields in AEDT +# +# Plot radiation patterns in AEDT + +new_report.report_type = "3D Polar Plot" +new_report.secondary_sweep = "Phi" +new_report.create("Realized3D") + +# ## Plot far fields outside AEDT +# +# Plot radiation patterns outside AEDT + +solutions_custom = new_report.get_solution_data() +solutions_custom.plot_3d() + +# ## Plot E Field on nets and layers +# +# Plot E Field on nets and layers in AEDT + +h3d.post.create_fieldplot_layers_nets( + [["TOP", "Array_antenna"]], + "Mag_E", + intrinsics={"Freq": "20GHz", "Phase": "0deg"}, + plot_name="E_Layers", +) + +# ## Close AEDT +# +# After the simulation completes, the application can be released from the +# :func:`pyaedt.Desktop.release_desktop` method. +# All methods provide for saving the project before closing AEDT. + +h3d.save_project(os.path.join(temp_dir.name, "test_layout.aedt")) +h3d.release_desktop() + +# ### Clean up the temporary directory +# +# The following command removes the project and the temporary directory. +# If you'd like to save this project, save it to a folder of your choice prior +# to running the following cell. + +temp_dir.cleanup() diff --git a/examples/00-EDB/04_edb_parametrized_design.py b/examples/00-EDB/04_edb_parametrized_design.py new file mode 100644 index 000000000..d0edeca28 --- /dev/null +++ b/examples/00-EDB/04_edb_parametrized_design.py @@ -0,0 +1,363 @@ +# # 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 +# +# + +# + +import os +import tempfile + +from ansys.pyaedt.examples.constants import EDB_VERSION +import pyaedt + +# - + +# ## Set non-graphical mode +# +# Set non-graphical mode. The default is ``False``, which opens +# the AEDT UI. + +non_graphical = False + +# ## Launch EDB. + +# + +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. "2023.2") +edb_version = EDB_VERSION +print(f"EDB version: {edb_version}") + +edb = pyaedt.Edb(edbpath=aedb_path, edbversion=edb_version) +# - + +# 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]) +# - + +# 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"}, +] + +# Create the EDB stackup. +# 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. + +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"], + ], +] + +# 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") + ) + +# Create the wave ports + +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", +) + +# 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) +# - + +# 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 = pyaedt.Hfss3dLayout( + projectname=aedb_path, + specified_version="2023.2", + non_graphical=non_graphical, + new_desktop_session=True, +) + +# # Add HFSS simulation setup +# +# Add HFSS simulation setup. + +# + +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, +) +# - + +# Define the differential pairs to used to calculate differential and common mode +# s-parameters. + +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" +) + +# Solve the project. + +h3d.analyze() + +# Plot the results and shut down AEDT. + +solutions = h3d.post.get_solution_data( + ["dB(S(In,In))", "dB(S(In,Out))"], context="Differential Pairs" +) +solutions.plot() +h3d.release_desktop() + +# 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_dir.cleanup() diff --git a/examples/00-EDB/05_Plot_nets.py b/examples/00-EDB/05_Plot_nets.py new file mode 100644 index 000000000..fac60c7da --- /dev/null +++ b/examples/00-EDB/05_Plot_nets.py @@ -0,0 +1,65 @@ +# # EDB: plot nets with Matplotlib +# +# This example shows how to use the ``Edb`` class to view nets, layers and +# via geometry directly in Python. The methods demonstrated in this example +# rely on +# [matplotlib](https://matplotlib.org/cheatsheets/_images/cheatsheets-1.png). + +# ## Perform required imports +# +# Perform required imports, which includes importing a section. + +# + +import tempfile + +from ansys.pyaedt.examples.constants import EDB_VERSION +import pyaedt + +# - + +# ## Download the EDB and copy it into the temporary folder. + +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +targetfolder = pyaedt.downloads.download_file("edb/ANSYS-HSD_V1.aedb", destination=temp_dir.name) + +# ## Create an instance of the Electronics Database using the `pyaedt.Edb` class. +# +# > Note that units are SI. + +# + +# Select EDB version (change it manually if needed, e.g. "2023.2") +edb_version = EDB_VERSION +print(f"EDB version: {edb_version}") + +edb = pyaedt.Edb(edbpath=targetfolder, edbversion=edb_version) +# - + +# Display the nets on a layer. You can display the net geometry directly in Python using +# ``matplotlib`` from the ``pyaedt.Edb`` class. + +edb.nets.plot("AVCC_1V3") + +# You can view multiple nets by passing a list containing the net +# names to the ``plot()`` method. + +edb.nets.plot(["GND", "GND_DP", "AVCC_1V3"], color_by_net=True) + +# You can display all copper on a single layer by passing ``None`` +# as the first argument. The second argument is a list +# of layers to plot. In this case, only one +# layer is to be displayed. + +edb.nets.plot(None, ["1_Top"], color_by_net=True, plot_components_on_top=True) + +# Display a side view of the layers and padstack geometry using the +# ``Edb.stackup.plot()`` method. + +edb.stackup.plot(scale_elevation=False, plot_definitions=["c100hn140", "c35"]) + +# Close the EDB. + +edb.close_edb() + +# Remove all files and the temporary directory. + +temp_dir.cleanup() diff --git a/examples/00-EDB/06_Advanced_EDB.py b/examples/00-EDB/06_Advanced_EDB.py new file mode 100644 index 000000000..982b7c367 --- /dev/null +++ b/examples/00-EDB/06_Advanced_EDB.py @@ -0,0 +1,175 @@ +# # EDB: parametric via creation +# +# This example shows how you can use EDB to create a layout. +# +# First import the required Python packages. + + +import os +import tempfile + +from ansys.pyaedt.examples.constants import EDB_VERSION +import numpy as np +import pyaedt + +# Create the EDB project. + +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +aedb_path = os.path.join(temp_dir.name, "parametric_via.aedb") + +# ## Create stackup +# +# The ``StackupSimple`` class creates a stackup based on few inputs. This stackup +# is used later. +# +# Define a function to create the ground conductor. + + +def create_ground_planes(edb, layers): + plane = edb.modeler.Shape("rectangle", pointA=["-3mm", "-3mm"], pointB=["3mm", "3mm"]) + for i in layers: + edb.modeler.create_polygon(plane, i, net_name="GND") + + +# ## Create the EDB +# +# Create the EDB instance. +# If the path doesn't exist, PyAEDT automatically generates a new AEDB folder. + +# + +# Select EDB version (change it manually if needed, e.g. "2023.2") +edb_version = EDB_VERSION +print(f"EDB version: {edb_version}") + +edb = pyaedt.Edb(edbpath=aedb_path, edbversion=edb_version) +# - + +# Insert the stackup layers. + +layout_count = 12 +diel_material_name = "FR4_epoxy" +diel_thickness = "0.15mm" +cond_thickness_outer = "0.05mm" +cond_thickness_inner = "0.017mm" +soldermask_thickness = "0.05mm" +trace_in_layer = "TOP" +trace_out_layer = "L10" +gvia_num = 10 +gvia_angle = 30 +edb.stackup.create_symmetric_stackup( + layer_count=layout_count, + inner_layer_thickness=cond_thickness_inner, + outer_layer_thickness=cond_thickness_outer, + soldermask_thickness=soldermask_thickness, + dielectric_thickness=diel_thickness, + dielectric_material=diel_material_name, +) + +# ## Define parameters +# +# Define parameters to allow changes in the model dimesons. Parameters preceded by +# the ``$`` character have project-wide scope. +# Without the ``$`` prefix, the parameter scope is limited to the design. + +# + +giva_angle_rad = gvia_angle / 180 * np.pi + +edb["$via_hole_size"] = "0.3mm" +edb["$antipaddiam"] = "0.7mm" +edb["$paddiam"] = "0.5mm" +edb.add_design_variable("via_pitch", "1mm", is_parameter=True) +edb.add_design_variable("trace_in_width", "0.2mm", is_parameter=True) +edb.add_design_variable("trace_out_width", "0.1mm", is_parameter=True) +# - + +# ## Define padstacks +# +# Create two padstck definitions, one for the ground via and one for the signal via. + +edb.padstacks.create( + padstackname="SVIA", + holediam="$via_hole_size", + antipaddiam="$antipaddiam", + paddiam="$paddiam", + start_layer=trace_in_layer, + stop_layer=trace_out_layer, +) +edb.padstacks.create(padstackname="GVIA", holediam="0.3mm", antipaddiam="0.7mm", paddiam="0.5mm") + +# Place the signal via. + +edb.padstacks.place([0, 0], "SVIA", net_name="RF") + +# Place the ground vias. + +# + +gvia_num_side = gvia_num / 2 + +if gvia_num_side % 2: + + # Even number of ground vias on each side + edb.padstacks.place(["via_pitch", 0], "GVIA", net_name="GND") + edb.padstacks.place(["via_pitch*-1", 0], "GVIA", net_name="GND") + for i in np.arange(1, gvia_num_side / 2): + xloc = "{}*{}".format(np.cos(giva_angle_rad * i), "via_pitch") + yloc = "{}*{}".format(np.sin(giva_angle_rad * i), "via_pitch") + edb.padstacks.place([xloc, yloc], "GVIA", net_name="GND") + edb.padstacks.place([xloc, yloc + "*-1"], "GVIA", net_name="GND") + + edb.padstacks.place([xloc + "*-1", yloc], "GVIA", net_name="GND") + edb.padstacks.place([xloc + "*-1", yloc + "*-1"], "GVIA", net_name="GND") +else: + + # Odd number of ground vias on each side + for i in np.arange(0, gvia_num_side / 2): + xloc = "{}*{}".format(np.cos(giva_angle_rad * (i + 0.5)), "via_pitch") + yloc = "{}*{}".format(np.sin(giva_angle_rad * (i + 0.5)), "via_pitch") + edb.padstacks.place([xloc, yloc], "GVIA", net_name="GND") + edb.padstacks.place([xloc, yloc + "*-1"], "GVIA", net_name="GND") + + edb.padstacks.place([xloc + "*-1", yloc], "GVIA", net_name="GND") + edb.padstacks.place([xloc + "*-1", yloc + "*-1"], "GVIA", net_name="GND") +# - + +# Draw the traces + +# + +edb.modeler.create_trace( + [[0, 0], [0, "-3mm"]], + layer_name=trace_in_layer, + net_name="RF", + width="trace_in_width", + start_cap_style="Flat", + end_cap_style="Flat", +) + +edb.modeler.create_trace( + [[0, 0], [0, "3mm"]], + layer_name=trace_out_layer, + net_name="RF", + width="trace_out_width", + start_cap_style="Flat", + end_cap_style="Flat", +) +# - + +# Draw ground conductors + +ground_layers = [i for i in edb.stackup.signal_layers.keys()] +ground_layers.remove(trace_in_layer) +ground_layers.remove(trace_out_layer) +create_ground_planes(edb=edb, layers=ground_layers) + +# Display the layout + +edb.stackup.plot(plot_definitions=["GVIA", "SVIA"]) + +# Save EDB and close the EDB. + +edb.save_edb() +edb.close_edb() +print("aedb Saved in {}".format(aedb_path)) + +# Clean up the temporary directory. + +temp_dir.cleanup() diff --git a/examples/00-EDB/08_CPWG.py b/examples/00-EDB/08_CPWG.py new file mode 100644 index 000000000..7481b6eba --- /dev/null +++ b/examples/00-EDB/08_CPWG.py @@ -0,0 +1,190 @@ +# # EDB: fully parametrized CPWG design + +# This example shows how you can use HFSS 3D Layout to create a parametric design +# for a CPWG (coplanar waveguide with ground). + + +# ## Perform required imports + +# Perform required imports. Importing the ``Hfss3dlayout`` object initializes it +# on version 2023 R2. + +import os + +from ansys.pyaedt.examples.constants import AEDT_VERSION, EDB_VERSION +import numpy as np +import pyaedt + +# ## Set non-graphical mode + +# Set non-graphical mode. The default is ``False``. + +non_graphical = False + +# ## Launch EDB + +# + +aedb_path = os.path.join( + pyaedt.generate_unique_folder_name(), pyaedt.generate_unique_name("pcb") + ".aedb" +) +print(aedb_path) + +# Select EDB version (change it manually if needed, e.g. "2023.2") +edb_version = EDB_VERSION +print(f"EDB version: {edb_version}") + +edbapp = pyaedt.Edb(edbpath=aedb_path, edbversion=edb_version) +# - + +# ## Define parameters + +params = { + "$ms_width": "0.4mm", + "$ms_clearance": "0.3mm", + "$ms_length": "20mm", +} +for par_name in params: + edbapp.add_project_variable(par_name, params[par_name]) + + +# ## Create s symmetric stackup + +edbapp.stackup.create_symmetric_stackup(2) +edbapp.stackup.plot() + +# ## Draw planes + +# + +plane_lw_pt = ["0mm", "-3mm"] +plane_up_pt = ["$ms_length", "3mm"] + +top_layer_obj = edbapp.modeler.create_rectangle( + "TOP", net_name="gnd", lower_left_point=plane_lw_pt, upper_right_point=plane_up_pt +) +bot_layer_obj = edbapp.modeler.create_rectangle( + "BOTTOM", net_name="gnd", lower_left_point=plane_lw_pt, upper_right_point=plane_up_pt +) +layer_dict = {"TOP": top_layer_obj, "BOTTOM": bot_layer_obj} +# - + +# ## Draw a trace + +trace_path = [["0", "0"], ["$ms_length", "0"]] +edbapp.modeler.create_trace( + trace_path, + layer_name="TOP", + width="$ms_width", + net_name="sig", + start_cap_style="Flat", + end_cap_style="Flat", +) + +# ## Create a trace to plane clearance + +poly_void = edbapp.modeler.create_trace( + trace_path, + layer_name="TOP", + net_name="gnd", + width="{}+2*{}".format("$ms_width", "$ms_clearance"), + start_cap_style="Flat", + end_cap_style="Flat", +) +edbapp.modeler.add_void(layer_dict["TOP"], poly_void) + +# ## Create a ground via padstack and place ground stitching vias + +# + +edbapp.padstacks.create( + padstackname="GVIA", + holediam="0.3mm", + paddiam="0.5mm", +) + +yloc_u = "$ms_width/2+$ms_clearance+0.25mm" +yloc_l = "-$ms_width/2-$ms_clearance-0.25mm" + +for i in np.arange(1, 20): + edbapp.padstacks.place([str(i) + "mm", yloc_u], "GVIA", net_name="GND") + edbapp.padstacks.place([str(i) + "mm", yloc_l], "GVIA", net_name="GND") +# - + +# ## Save and close EDB + +edbapp.save_edb() +edbapp.close_edb() + +# ## Open EDB in AEDT + +h3d = pyaedt.Hfss3dLayout( + projectname=aedb_path, + specified_version=AEDT_VERSION, + non_graphical=non_graphical, + new_desktop_session=True, +) + +# ## Create wave ports + +h3d.create_edge_port( + "line_3", 0, iswave=True, wave_vertical_extension=10, wave_horizontal_extension=10 +) +h3d.create_edge_port( + "line_3", 2, iswave=True, wave_vertical_extension=10, wave_horizontal_extension=10 +) + +# ## Edit airbox extents + +h3d.edit_hfss_extents(air_vertical_positive_padding="10mm", air_vertical_negative_padding="1mm") + +# ## Create setup + +setup = h3d.create_setup() +setup["MaxPasses"] = 2 +setup["AdaptiveFrequency"] = "3GHz" +setup["SaveAdaptiveCurrents"] = True +h3d.create_linear_count_sweep( + setupname=setup.name, + unit="GHz", + freqstart=0, + freqstop=5, + 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, +) + +# ## Plot layout + +# + +h3d.modeler.edb.nets.plot(None, None, color_by_net=True) + +cp_name = h3d.modeler.clip_plane() +# - + +# ## Solve the active design + +# Uncomment the following code to start the HFSS solver and perform post processing. + +# + +# h3d.analyze() + +# solutions = h3d.get_touchstone_data()[0] +# solutions.log_x = False +# solutions.plot() + +# h3d.post.create_fieldplot_cutplane( +# cp_name, "Mag_E", h3d.nominal_adaptive, intrinsincDict={"Freq": "3GHz", "Phase": "0deg"} +# ) +# - + +# ## Save AEDT + +aedt_path = aedb_path.replace(".aedb", ".aedt") +h3d.logger.info("Your AEDT project is saved to {}".format(aedt_path)) +h3d.save_project() + +# ## Release AEDT + +h3d.release_desktop() diff --git a/examples/00-EDB/09_Configuration.py b/examples/00-EDB/09_Configuration.py new file mode 100644 index 000000000..286ebff0e --- /dev/null +++ b/examples/00-EDB/09_Configuration.py @@ -0,0 +1,226 @@ +# # EDB: Pin to Pin project +# +# This example demonstrates the use of the Electronics +# Database (EDB) interface to create a layout using the BOM and +# a configuration file. + +# ## Perform required imports +# +# The ``Hfss3dlayout`` class provides an interface to +# the 3D Layout editor in AEDT. +# on version 2023 R2. + +# + +import os +import tempfile + +from ansys.pyaedt.examples.constants import EDB_VERSION +import pyaedt + +# - + +# Download the AEDB file and copy it to a temporary folder. + +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +target_aedb = pyaedt.downloads.download_file("edb/ANSYS-HSD_V1.aedb", destination=temp_dir.name) +print("Project folder is", target_aedb) + +# ## Launch EDB +# +# Launch the ``pyaedt.Edb`` class using EDB 2023 R2. Length units are SI. + +# + +# Select EDB version (change it manually if needed, e.g. "2023.2") +edb_version = EDB_VERSION +print(f"EDB version: {edb_version}") + +edbapp = pyaedt.Edb(target_aedb, edbversion=edb_version) +# - + +# ## Import definitions +# +# The definition file uses the [json](https://www.json.org/json-en.html) to +# map layout part numbers to their corresponding models. +# +# The model may be an RLC, S-parameter, or +# [SPICE](https://en.wikipedia.org/wiki/SPICE) model definition. +# Once imported, the definition is applied to the components in the layout. +# In this example, the JSON file is in the ``*.aedb`` folder and has the following format: +# ``` json +# { +# "SParameterModel": { +# "GRM32_DC0V_25degC_series": "./GRM32_DC0V_25degC_series.s2p" +# }, +# "SPICEModel": { +# "GRM32_DC0V_25degC": "./GRM32_DC0V_25degC.mod" +# }, +# "Definitions": { +# "CAPC1005X05N": { +# "Component_type": "Capacitor", +# "Model_type": "RLC", +# "Res": 1, +# "Ind": 2, +# "Cap": 3, +# "Is_parallel": false +# }, +# "'CAPC3216X180X55ML20T25": { +# "Component_type": "Capacitor", +# "Model_type": "SParameterModel", +# "Model_name": "GRM32_DC0V_25degC_series" +# }, +# "'CAPC3216X180X20ML20": { +# "Component_type": "Capacitor", +# "Model_type": "SPICEModel", +# "Model_name": "GRM32_DC0V_25degC" +# } +# } +# } +# ``` +# +# The ``Edb.components.import_definitions()`` method imports the component definitions that map +# electrical models to the components in the simulation model. + +edbapp.components.import_definition(os.path.join(target_aedb, "1_comp_definition.json")) + +# ## Import BOM +# +# The bill of materials (BOM) file provides the list of all components +# by reference designator, part name, component type, and nominal value. +# +# Components that are not contained in the BOM are deactivated in the +# simulation model. +# This example saves the CSV file in the ``aedb`` folder. +# +# ``` +# +------------+-----------------------+-----------+------------+ +# | RefDes | Part name | Type | Value | +# +============+=======================+===========+============+ +# | C380 | CAPC1005X55X25LL05T10 | Capacitor | 11nF | +# +------------+-----------------------+-----------+------------+ +# ``` +# +# Having red the information in the BOM and definitions file, electrical models can be +# assigned to all of the components in the simulation model. + +edbapp.components.import_bom( + os.path.join(target_aedb, "0_bom.csv"), + refdes_col=0, + part_name_col=1, + comp_type_col=2, + value_col=3, +) + +# ## Verify a Component +# +# Component property allows to access all components instances and their property with +# getters and setters. + +comp = edbapp.components["C1"] +comp.model_type, comp.value + +# ## Check component definition +# +# When an s-parameter model is associated to a component it will be available in +# nport_comp_definition property. + +edbapp.components.nport_comp_definition +edbapp.save_edb() + +# ## Configure the simulation setup +# +# This step enables the following: + +# - Definition of the nets to include in the cutout region +# - Cutout details +# - Components to create the ports on +# - Simulation settings +# +# The ``Edb.new_simulaton_configuration()`` method returns an instance +# of the ``SimulationConfiguration`` class. + +# + +sim_setup = edbapp.new_simulation_configuration() +sim_setup.solver_type = sim_setup.SOLVER_TYPE.SiwaveSYZ +sim_setup.batch_solve_settings.cutout_subdesign_expansion = 0.003 +sim_setup.batch_solve_settings.do_cutout_subdesign = True +sim_setup.batch_solve_settings.use_pyaedt_cutout = True +sim_setup.ac_settings.max_arc_points = 6 +sim_setup.ac_settings.max_num_passes = 5 + +sim_setup.batch_solve_settings.signal_nets = [ + "PCIe_Gen4_TX2_CAP_P", + "PCIe_Gen4_TX2_CAP_N", + "PCIe_Gen4_TX2_P", + "PCIe_Gen4_TX2_N", +] +sim_setup.batch_solve_settings.components = ["U1", "X1"] +sim_setup.batch_solve_settings.power_nets = ["GND", "GND_DP"] +sim_setup.ac_settings.start_freq = "100Hz" +sim_setup.ac_settings.stop_freq = "6GHz" +sim_setup.ac_settings.step_freq = "10MHz" +# - + +# ## Implement the setup +# +# The cutout and all other simulation settings are applied to the simulation model. + +sim_setup.export_json(os.path.join(temp_dir.name, "configuration.json")) +edbapp.build_simulation_project(sim_setup) + +# ## Display the cutout +# +# Plot cutout once finished. The model is ready to simulate. + +edbapp.nets.plot(None, None) + +# ## Save and close EDB +# +# EDB is saved and re-opened in HFSS +# 3D Layout, where the HFSS simulation can be run. + +edbapp.save_edb() +edbapp.close_edb() + +# ## Open Electronics Desktop +# +# The EDB is opened in AEDT Hfss3DLayout. +# +# Set ``non_graphical=True`` to run the simulation in non-graphical mode. + +h3d = pyaedt.Hfss3dLayout( + specified_version="2023.2", + projectname=target_aedb, + non_graphical=False, + new_desktop_session=False, +) + +# ## Analyze +# +# This project is ready to solve. +# Executing the following cell runs the HFSS simulation on the layout. + +h3d.analyze() + +# ## View results +# +# S-parameter data is loaded at the end of simulation. + +solutions = h3d.post.get_solution_data() + +# ## Plot results +# +# Plot S-Parameter data. + +solutions.plot(solutions.expressions, "db20") + +# ## Save and close AEDT +# +# HFSS 3D Layout is saved and closed. + +h3d.save_project() +h3d.release_desktop() + +# Clean up the temporary directory. All files and the temporary project +# folder will be deleted in the next step. + +temp_dir.cleanup() diff --git a/examples/00-EDB/10_GDS_workflow.py b/examples/00-EDB/10_GDS_workflow.py new file mode 100644 index 000000000..894286a54 --- /dev/null +++ b/examples/00-EDB/10_GDS_workflow.py @@ -0,0 +1,121 @@ +# # EDB: Edit Control File and import gds +# +# This example demonstrates how to import a gds layout for subsequent +# simulation with HFSS. + +# Perform imports. + +# + +import os +import shutil +import tempfile + +from ansys.pyaedt.examples.constants import EDB_VERSION +import pyaedt +from pyaedt.edb_core.edb_data.control_file import ControlFile + +# - + +# ## Fetch Example Data +# +# Download the EDB folder and copy it to a temporary folder. +# The following files are used in this example: +# - _sky130_fictious_dtc_exmple_contol_no_map.xml_ +# defines physical information such +# as material properties, stackup layers, and boundary conditions. +# - _dummy_layermap.map_ +# maps properties to stackup layers. + +# + +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +control_fn = "sky130_fictitious_dtc_example_control_no_map.xml" +gds_fn = "sky130_fictitious_dtc_example.gds" +layer_map = "dummy_layermap.map" + +local_path = pyaedt.downloads.download_file("gds", destination=temp_dir.name) +c_file_in = os.path.join(local_path, control_fn) +c_map = os.path.join(local_path, layer_map) +gds_in = os.path.join(local_path, gds_fn) +gds_out = os.path.join(temp_dir.name, "gds_out.gds") +shutil.copy2(gds_in, gds_out) +# - + +# ## Control file +# +# A Control file is an xml file which purpose if to provide additional information during +# import phase. It can include, materials, stackup, setup, boundaries and settings. +# In this example we will import an existing xml, integrate it with a layer mapping file of gds +# and then adding setup and boundaries. + +c = ControlFile(c_file_in, layer_map=c_map) + +# ## Set up simulation +# +# This code sets up a simulation with HFSS and adds a frequency sweep. + +setup = c.setups.add_setup("Setup1", "1GHz") +setup.add_sweep("Sweep1", "0.01GHz", "5GHz", "0.1GHz") + +# ## Provide additional stackup settings +# +# After import, you can change the stackup settings and add or remove layers or materials. + +c.stackup.units = "um" +c.stackup.dielectrics_base_elevation = -100 +c.stackup.metal_layer_snapping_tolerance = "10nm" +for via in c.stackup.vias: + via.create_via_group = True + via.snap_via_group = True + +# ## Define boundary settings +# +# Boundaries can include ports, components and boundary extent. + +c.boundaries.units = "um" +c.boundaries.add_port("P1", x1=223.7, y1=222.6, layer1="Metal6", x2=223.7, y2=100, layer2="Metal6") +c.boundaries.add_extent() +comp = c.components.add_component("B1", "BGA", "IC", "Flip chip", "Cylinder") +comp.solder_diameter = "65um" +comp.add_pin("1", "81.28", "84.6", "met2") +comp.add_pin("2", "211.28", "84.6", "met2") +comp.add_pin("3", "211.28", "214.6", "met2") +comp.add_pin("4", "81.28", "214.6", "met2") +c.import_options.import_dummy_nets = True + +# ## Write XML file +# +# After all settings are ready, you can write an XML file. + +c.write_xml(os.path.join(temp_dir.name, "output.xml")) + +# ## Open EDB +# +# Import the gds and open the edb. + +# + +from pyaedt import Edb + +# Select EDB version (change it manually if needed, e.g. "2023.2") +edb_version = EDB_VERSION +print(f"EDB version: {edb_version}") + +edb = Edb( + gds_out, edbversion=edb_version, technology_file=os.path.join(temp_dir.name, "output.xml") +) +# - + +# ## Plot stackup +# +# Plot the stackup. + +edb.stackup.plot(first_layer="met1") + +# ## Close EDB +# +# Close the project. + +edb.close_edb() + +# Clean up the temporary folder. + +temp_dir.cleanup() diff --git a/examples/00-EDB/11_post_layout_parameterization.py b/examples/00-EDB/11_post_layout_parameterization.py new file mode 100644 index 000000000..30a5d40ee --- /dev/null +++ b/examples/00-EDB/11_post_layout_parameterization.py @@ -0,0 +1,93 @@ +# # EDB: post-layout parameterization +# +# This example shows you how to parameterize the signal net in post-layout. +# +# Define input parameters. + +signal_net_name = "DDR4_ALERT3" +coplanar_plane_net_name = "1V0" # Specify name of coplanar plane net for adding clearance +layers = ["16_Bottom"] # Specify layers to parameterize + +# Perform required imports. + +# + +import os +import tempfile + +from ansys.pyaedt.examples.constants import EDB_VERSION +import pyaedt + +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") + +# Download and open example layout file in edb format. + +edb_path = pyaedt.downloads.download_file("edb/ANSYS-HSD_V1.aedb", destination=temp_dir.name) + +# Select EDB version (change it manually if needed, e.g. "2023.2") +edb_version = EDB_VERSION +print(f"EDB version: {edb_version}") + +edb = pyaedt.Edb(edb_path, edbversion=edb_version) +# - + +# ## Create cutout +# +# The ``Edb.cutout()`` method takes a list of +# signal nets as the first argument and a list of +# reference nets as the second argument. + +edb.cutout([signal_net_name], [coplanar_plane_net_name, "GND"], remove_single_pin_components=True) + +# Retrieve the path segments from the signal net. + +net = edb.nets[signal_net_name] +trace_segments = [] +for p in net.primitives: + if p.layer_name not in layers: + continue + if not p.type == "Path": + continue + trace_segments.append(p) + +# Create and assign delta w variable per layer. + +for p in trace_segments: + vname = f"{p.net_name}_{p.layer_name}_dw" + if vname not in edb.variables: + edb[vname] = "0mm" + new_w = f"{p.width}+{vname}" + p.width = new_w + +# Delete existing clearance. + +for p in trace_segments: + for g in edb.modeler.get_polygons_by_layer(p.layer_name, coplanar_plane_net_name): + for v in g.voids: + if p.is_intersecting(v): + v.delete() + +# Create and assign the clearance variable for each layer. + +for p in trace_segments: + clr = f"{p.net_name}_{p.layer_name}_clr" + if clr not in edb.variables: + edb[clr] = "0.5mm" + path = p.get_center_line() + for g in edb.modeler.get_polygons_by_layer(p.layer_name, coplanar_plane_net_name): + void = edb.modeler.create_trace(path, p.layer_name, f"{p.width}+{clr}*2") + g.add_void(void) + +# Visualize the layout. + +edb.nets.plot(layers=layers[0], size=2000) + +# Save the AEDB file and close EDB. + +save_edb_path = os.path.join(temp_dir.name, "post_layout_parameterization.aedb") +edb.save_edb_as(save_edb_path) +print("Edb is saved to ", save_edb_path) +edb.close_edb() + +# Clean up the temporary folder. + +temp_dir.cleanup() diff --git a/examples/00-EDB/12_edb_sma_connector_on_board.py b/examples/00-EDB/12_edb_sma_connector_on_board.py new file mode 100644 index 000000000..ddfe361bc --- /dev/null +++ b/examples/00-EDB/12_edb_sma_connector_on_board.py @@ -0,0 +1,220 @@ +# # EDB: geometry creation +# +# This example shows how to +# 1. Create a parameterized PCB with an SMA connector footprint for a single-ended +# SMA connector launch footprint.. +# 2. Place 3D component on PCB. +# 3. Create HFSS setup and frequency sweep with a mesh operation. +# 4. Create return loss plot + +# ## See the finished project +# +# + +# ## Create a parameterized PCB +# +# Import dependencies. + +import os +import tempfile + +from ansys.pyaedt.examples.constants import EDB_VERSION +import numpy as np +import pyaedt + +# Create the EDB. + +# + +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +working_folder = temp_dir.name + +# Select EDB version (change it manually if needed, e.g. "2023.2") +edb_version = EDB_VERSION +print(f"EDB version: {edb_version}") + +aedb_path = os.path.join(working_folder, "pcb.aedb") +print("AEDB file is located in {}".format(aedb_path)) + +edb = pyaedt.Edb(edbpath=aedb_path, edbversion=edb_version) +# - + +# Add the FR4 dielectric for the PCB. + +edb.materials.add_dielectric_material("ANSYS_FR4", 3.5, 0.005) + +# ## Create Stackup +# +# While this code explicitly defines the stackup, you can import it +# from a from a CSV or XML file using the +# ``Edb.stackup.import_stackup()`` method. + +edb.add_design_variable("$DIEL_T", "0.15mm") +edb.stackup.add_layer("BOT") +edb.stackup.add_layer( + "D5", "GND", layer_type="dielectric", thickness="$DIEL_T", material="ANSYS_FR4" +) +edb.stackup.add_layer("L5", "Diel", thickness="0.05mm") +edb.stackup.add_layer( + "D4", "GND", layer_type="dielectric", thickness="$DIEL_T", material="ANSYS_FR4" +) +edb.stackup.add_layer("L4", "Diel", thickness="0.05mm") +edb.stackup.add_layer( + "D3", "GND", layer_type="dielectric", thickness="$DIEL_T", material="ANSYS_FR4" +) +edb.stackup.add_layer("L3", "Diel", thickness="0.05mm") +edb.stackup.add_layer( + "D2", "GND", layer_type="dielectric", thickness="$DIEL_T", material="ANSYS_FR4" +) +edb.stackup.add_layer("L2", "Diel", thickness="0.05mm") +edb.stackup.add_layer( + "D1", "GND", layer_type="dielectric", thickness="$DIEL_T", material="ANSYS_FR4" +) +edb.stackup.add_layer("TOP", "Diel", thickness="0.05mm") + +# Create ground conductors. + +# + +edb.add_design_variable("PCB_W", "20mm") +edb.add_design_variable("PCB_L", "20mm") + +gnd_dict = {} +for layer_name in edb.stackup.signal_layers.keys(): + gnd_dict[layer_name] = edb.modeler.create_rectangle( + layer_name, "GND", [0, "PCB_W/-2"], ["PCB_L", "PCB_W/2"] + ) +# - + +# ## Create signal net +# +# Create signal net on layer 3, and add clearance to the ground plane. + +# + +edb.add_design_variable("SIG_L", "10mm") +edb.add_design_variable("SIG_W", "0.1mm") +edb.add_design_variable("SIG_C", "0.3mm") + +signal_path = (["5mm", 0], ["SIG_L+5mm", 0]) +signal_trace = edb.modeler.create_trace(signal_path, "L3", "SIG_W", "SIG", "Flat", "Flat") + +signal_path = (["5mm", 0], ["PCB_L", 0]) +clr = edb.modeler.create_trace(signal_path, "L3", "SIG_C*2+SIG_W", "SIG", "Flat", "Flat") +gnd_dict["L3"].add_void(clr) +# - + +# ## Place signal vias +# +# Create the via padstack definition and place the signal vias. + +edb.add_design_variable("SG_VIA_D", "1mm") +edb.add_design_variable("$VIA_AP_D", "1.2mm") +edb.padstacks.create("ANSYS_VIA", "0.3mm", "0.5mm", "$VIA_AP_D") +edb.padstacks.place(["5mm", 0], "ANSYS_VIA", "SIG") + +# Create ground vias around the SMA +# connector launch footprint. The vias +# are placed around the circumference +# of the launch from 35 degrees to 325 +# degrees. + +for i in np.arange(30, 326, 35): + px = np.cos(i / 180 * np.pi) + py = np.sin(i / 180 * np.pi) + edb.padstacks.place( + ["{}*{}+5mm".format("SG_VIA_D", px), "{}*{}".format("SG_VIA_D", py)], "ANSYS_VIA", "GND" + ) + +# Create ground vias along the signal trace. + +for i in np.arange(2e-3, edb.variables["SIG_L"].value - 2e-3, 2e-3): + edb.padstacks.place(["{}+5mm".format(i), "1mm"], "ANSYS_VIA", "GND") + edb.padstacks.place(["{}+5mm".format(i), "-1mm"], "ANSYS_VIA", "GND") + +# Create a wave port at the end of the signal trace. + +signal_trace.create_edge_port("port_1", "End", "Wave", horizontal_extent_factor=10) + +# ## Set up HFSS simulation +# +# The ``max_num_passes`` argument sets an upper limit on the +# number of adaptive passes for mesh refinement. +# +# For broadband applications when the simulation results may be used +# to generate a SPICE model, the outer domain boundary can be +# located roughly $$ d=\lambda/8 $$ from the internal structures +# in the model. + +# + +extend_domain = 3e11 / 5e9 / 8.0 # Quarter wavelength at 4 GHz. +edb.design_options.antipads_always_on = True +edb.hfss.hfss_extent_info.air_box_horizontal_extent = extend_domain +edb.hfss.hfss_extent_info.air_box_positive_vertical_extent = extend_domain +edb.hfss.hfss_extent_info.air_box_negative_vertical_extent = extend_domain + +setup = edb.create_hfss_setup("Setup1") +setup.set_solution_single_frequency("5GHz", max_num_passes=8, max_delta_s="0.02") +setup.hfss_solver_settings.order_basis = "first" +# - + +# Add a mesh operation to the setup. + +edb.setups["Setup1"].add_length_mesh_operation({"SIG": ["L3"]}, "m1", max_length="0.1mm") + +# Add a frequency sweep to setup. +# +# When the simulation results are to +# be used for transient SPICE analysis, you should +# use the following strategy: +# +# - DC point +# - Logarithmic sweep from 1 kHz to 100 MHz +# - Linear scale for higher frequencies. + +setup.add_frequency_sweep( + "Sweep1", + frequency_sweep=[ + ["linear count", "0", "1KHz", 1], + ["log scale", "1KHz", "100MHz", 10], + ["linear scale", "0.1GHz", "5GHz", "0.1GHz"], + ], +) + +# Save and close EDB. + +edb.save_edb() +edb.close_edb() + +# Launch HFSS 3D Layout. + +h3d = pyaedt.Hfss3dLayout(aedb_path, specified_version=edb_version, new_desktop_session=True) + +# Place a 3D component. + +full_comp_name = pyaedt.downloads.download_file( + "component_3d", filename="SMA_RF_SURFACE_MOUNT.a3dcomp", destination=working_folder +) +comp = h3d.modeler.place_3d_component( + component_path=full_comp_name, + number_of_terminals=1, + placement_layer="TOP", + component_name="my_connector", + pos_x="5mm", + pos_y=0.000, +) + +# ## Run simulation + +h3d.analyze(num_cores=4) + +# ## Visualize the return loss. + +h3d.post.create_report("dB(S(port_1, port_1))") + +# ## Save and close the project. + +h3d.save_project() +print("Project is saved to {}".format(h3d.project_path)) +h3d.release_desktop(True, True) + +# ## Clean up the temporary folder. + +temp_dir.cleanup() diff --git a/examples/00-EDB/13_edb_create_component.py b/examples/00-EDB/13_edb_create_component.py new file mode 100644 index 000000000..c5c590183 --- /dev/null +++ b/examples/00-EDB/13_edb_create_component.py @@ -0,0 +1,253 @@ +# # EDB: Layout Creation and Setup +# +# This example demonstrates how to to +# +# 1. Create a layout layer stackup. +# 2. Define padstacks. +# 3. Place padstack instances in the layout where the connectors are located. +# 4. Create primitives such as polygons and traces. +# 5. Create "components" from the padstack definitions using "pins". +# >The "component" in EDB acts as a placeholder to enable automatic +# >placement of electrical models, or +# >as in this example to assign ports. In many +# >cases the EDB is imported from a 3rd party layout, in which case the +# >concept of a "component" as a placeholder is needed to map +# >models to the components on the PCB for later use in the +# >simulation. +# 7. Create the HFSS simulation setup and assign ports where the connectors are located. + +# ## View PCB trace model +# +# Here is an image of the model that is created in this example. +# +# +# +# The rectangular sheets at each end of the PCB enable placement of ports where the connectors +# are located. + +# Initialize the EDB layout object. + +# + +import os +import tempfile + +from ansys.pyaedt.examples.constants import EDB_VERSION +import pyaedt +from pyaedt import Edb + +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +aedb_path = os.path.join(temp_dir.name, "component_example.aedb") + +# Select EDB version (change it manually if needed, e.g. "2023.2") +edb_version = EDB_VERSION +print(f"EDB version: {edb_version}") + +edb = Edb(edbpath=aedb_path, edbversion=edb_version) +print("EDB is located at {}".format(aedb_path)) +# - + +# Initialize variables + +layout_count = 12 +diel_material_name = "FR4_epoxy" +diel_thickness = "0.15mm" +cond_thickness_outer = "0.05mm" +cond_thickness_inner = "0.017mm" +soldermask_thickness = "0.05mm" +trace_in_layer = "TOP" +trace_out_layer = "L10" +trace_width = "200um" +connector_size = 2e-3 +conectors_position = [[0, 0], [10e-3, 0]] + +# Create the stackup + +edb.stackup.create_symmetric_stackup( + layer_count=layout_count, + inner_layer_thickness=cond_thickness_inner, + outer_layer_thickness=cond_thickness_outer, + soldermask_thickness=soldermask_thickness, + dielectric_thickness=diel_thickness, + dielectric_material=diel_material_name, +) + +# Create ground planes + +ground_layers = [ + layer_name + for layer_name in edb.stackup.signal_layers.keys() + if layer_name not in [trace_in_layer, trace_out_layer] +] +plane_shape = edb.modeler.Shape("rectangle", pointA=["-3mm", "-3mm"], pointB=["13mm", "3mm"]) +for i in ground_layers: + edb.modeler.create_polygon(plane_shape, i, net_name="VSS") + +# ### Add design parameters +# +# Parameters that are preceded by a _"$"_ character have project-wide scope. +# Therefore, the padstack **definition** and hence all instances of that padstack +# rely on the parameters. +# +# Parameters such as _"trace_in_width"_ and _"trace_out_width"_ have local scope and +# are only used in in the design. + +edb.add_design_variable("$via_hole_size", "0.3mm") +edb.add_design_variable("$antipaddiam", "0.7mm") +edb.add_design_variable("$paddiam", "0.5mm") +edb.add_design_variable("trace_in_width", "0.2mm", is_parameter=True) +edb.add_design_variable("trace_out_width", "0.1mm", is_parameter=True) + +# ### Create the connector component +# +# The component definition is used to place the connector on the PCB. First define the padstacks. + +edb.padstacks.create_padstack( + padstackname="Via", holediam="$via_hole_size", antipaddiam="$antipaddiam", paddiam="$paddiam" +) + +# Create the first connector + +component1_pins = [ + edb.padstacks.place_padstack( + conectors_position[0], + "Via", + net_name="VDD", + fromlayer=trace_in_layer, + tolayer=trace_out_layer, + ), + edb.padstacks.place_padstack( + [ + conectors_position[0][0] - connector_size / 2, + conectors_position[0][1] - connector_size / 2, + ], + "Via", + net_name="VSS", + ), + edb.padstacks.place_padstack( + [ + conectors_position[0][0] + connector_size / 2, + conectors_position[0][1] - connector_size / 2, + ], + "Via", + net_name="VSS", + ), + edb.padstacks.place_padstack( + [ + conectors_position[0][0] + connector_size / 2, + conectors_position[0][1] + connector_size / 2, + ], + "Via", + net_name="VSS", + ), + edb.padstacks.place_padstack( + [ + conectors_position[0][0] - connector_size / 2, + conectors_position[0][1] + connector_size / 2, + ], + "Via", + net_name="VSS", + ), +] + +# Create the second connector + +component2_pins = [ + edb.padstacks.place_padstack( + conectors_position[-1], + "Via", + net_name="VDD", + fromlayer=trace_in_layer, + tolayer=trace_out_layer, + ), + edb.padstacks.place_padstack( + [ + conectors_position[1][0] - connector_size / 2, + conectors_position[1][1] - connector_size / 2, + ], + "Via", + net_name="VSS", + ), + edb.padstacks.place_padstack( + [ + conectors_position[1][0] + connector_size / 2, + conectors_position[1][1] - connector_size / 2, + ], + "Via", + net_name="VSS", + ), + edb.padstacks.place_padstack( + [ + conectors_position[1][0] + connector_size / 2, + conectors_position[1][1] + connector_size / 2, + ], + "Via", + net_name="VSS", + ), + edb.padstacks.place_padstack( + [ + conectors_position[1][0] - connector_size / 2, + conectors_position[1][1] + connector_size / 2, + ], + "Via", + net_name="VSS", + ), +] + +# ### Define pins +# +# Pins are fist defined to allow a component to subsequently connect to the remainder +# of the model. In this case, ports are assigned at the connector instances using the pins. + +for padstack_instance in list(edb.padstacks.instances.values()): + padstack_instance.is_pin = True + +# Create components from the pins + +edb.components.create(component1_pins, "connector_1") +edb.components.create(component2_pins, "connector_2") + +# Create ports on the pins and insert a simulation setup using the +# ``SimulationConfiguration`` class. + +sim_setup = edb.new_simulation_configuration() +sim_setup.solver_type = sim_setup.SOLVER_TYPE.Hfss3dLayout +sim_setup.batch_solve_settings.cutout_subdesign_expansion = 0.01 +sim_setup.batch_solve_settings.do_cutout_subdesign = False +sim_setup.batch_solve_settings.signal_nets = ["VDD"] +sim_setup.batch_solve_settings.components = ["connector_1", "connector_2"] +sim_setup.batch_solve_settings.power_nets = ["VSS"] +sim_setup.ac_settings.start_freq = "0GHz" +sim_setup.ac_settings.stop_freq = "5GHz" +sim_setup.ac_settings.step_freq = "1GHz" +edb.build_simulation_project(sim_setup) + +# Save the EDB and open it in the 3D Layout editor. If ``non_graphical==False``, +# there may be a delay while AEDT starts. + +edb.save_edb() +edb.close_edb() +h3d = pyaedt.Hfss3dLayout( + specified_version="2023.2", + projectname=aedb_path, + non_graphical=False, # Set non_graphical = False to launch AEDT in graphical mode. + new_desktop_session=True, +) + +# ### Release the application from the Python kernel +# +# It is important to release the application from the Python kernel after +# execution of the script. The default behavior of the ``release_desktop()`` method closes all open +# projects and closes the application. +# +# If you want to conintue working on the project in graphical mode +# after script execution, call the following method with both arguments set to ``False``. + +h3d.release_desktop(close_projects=True, close_desktop=True) + +# ### Clean up the temporary directory +# +# The following command cleans up the temporary directory, thereby removing all +# project files. If you'd like to save this project, save it to a folder of your choice +# prior to running the following cell. + +temp_dir.cleanup() diff --git a/examples/00-EDB/14_edb_create_parametrized_design.py b/examples/00-EDB/14_edb_create_parametrized_design.py new file mode 100644 index 000000000..802c2b425 --- /dev/null +++ b/examples/00-EDB/14_edb_create_parametrized_design.py @@ -0,0 +1,113 @@ +# # EDB: parameterized design +# +# This example shows how to +# 1. Set up an HFSS project using SimulationConfiguration class. +# 2. Create automatically parametrized design. +# +# This image shows the layout created in this example: +# +# +# + +# ## Import dependencies. + +import tempfile + +from ansys.pyaedt.examples.constants import EDB_VERSION +import pyaedt + +# ## Create an instance of a pyaedt.Edb object. + +# + +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +target_aedb = pyaedt.downloads.download_file("edb/ANSYS-HSD_V1.aedb", destination=temp_dir.name) +print("Project is located in ", target_aedb) + +# Select EDB version (change it manually if needed, e.g. "2023.2") +edb_version = EDB_VERSION +print(f"EDB version: {edb_version}") + +edb = pyaedt.Edb(edbpath=target_aedb, edbversion=edb_version) +print("AEDB file is located in {}".format(target_aedb)) +# - + +# ## Prepare the layout for the simulation +# +# The ``new_simulation_configuration()`` method creates an instance of +# the ``SimulationConfiguration`` class. This class helps define all preprocessing steps +# required to set up the PCB for simulation. After the simulation configuration has been defined, +# they are applied to the EDB using the ``Edb.build_simulation()`` method. + +simulation_configuration = edb.new_simulation_configuration() +simulation_configuration.signal_nets = [ + "PCIe_Gen4_RX0_P", + "PCIe_Gen4_RX0_N", + "PCIe_Gen4_RX1_P", + "PCIe_Gen4_RX1_N", +] +simulation_configuration.power_nets = ["GND"] +simulation_configuration.components = ["X1", "U1"] +simulation_configuration.do_cutout_subdesign = True +simulation_configuration.start_freq = "OGHz" +simulation_configuration.stop_freq = "20GHz" +simulation_configuration.step_freq = "10MHz" + +# Now apply the simulation setup to the EDB. + +edb.build_simulation_project(simulation_configuration) + +# ## Parameterize +# +# The layout can automatically be set up to enable parametric studies. For example, the +# impact of antipad diameter or trace width on signal integrity performance may be invested +# parametrically. + +edb.auto_parametrize_design( + layers=True, materials=True, via_holes=True, pads=True, antipads=True, traces=True +) +edb.save_edb() +edb.close_edb() + +# ## Open project in AEDT +# +# All manipulations thus far have been executed using the EDB API, which provides fast, +# streamlined processing of layout data in non-graphical mode. The layout and simulation +# setup can be visualized by opening it using the 3D Layout editor in AEDT. +# +# Note that there may be some delay while AEDT is being launched. + +hfss = pyaedt.Hfss3dLayout( + projectname=target_aedb, + specified_version=edb_version, + non_graphical=False, + new_desktop_session=True, +) + +# The following cell can be used to ensure that the design is valid for simulation. + +validation_info = hfss.validate_full_design() +is_ready_to_simulate = True + +# + +for s in validation_info[0]: + if "error" in s: + print(s) + is_ready_to_simulate = False + +if is_ready_to_simulate: + print("The model is ready for simulation.") +else: + print("There are errors in the model that must be fixed.") +# - + +# ## Release the application from the Python kernel +# +# It is important to release the application from the Python kernel after +# execution of the script. The default behavior of the ``release_desktop()`` method closes all open +# projects and closes the application. +# +# If you want to continue working on the project in graphical mode +# after script execution, call the following method with both arguments set to ``False``. + +hfss.release_desktop(close_projects=True, close_desktop=True) +temp_dir.cleanup() # Remove the temporary folder and files. All data will be removd! diff --git a/examples/00-EDB/15_ac_analysis.py b/examples/00-EDB/15_ac_analysis.py new file mode 100644 index 000000000..d9b48248a --- /dev/null +++ b/examples/00-EDB/15_ac_analysis.py @@ -0,0 +1,179 @@ +# # EDB: Network Analysis in SIwave +# +# This example shows how to use PyAEDT to set up SYZ analysis on a +# [serdes](https://en.wikipedia.org/wiki/SerDes) channel. +# The signal input is applied differetially. The positive net is _"PCIe_Gen4_TX3_CAP_P"_. +# The negative net is _"PCIe_Gen4_TX3_CAP_N"_. In this example, ports are placed on the +# driver and +# receiver components. + +# ### Perform required imports +# +# Perform required imports, which includes importing a section. + +import tempfile +import time + +from ansys.pyaedt.examples.constants import EDB_VERSION +import pyaedt + +# ### Download file +# +# Download the AEDB file and copy it in the temporary folder. + +# + +temp_dir = tempfile.TemporaryDirectory(suffix=".ansys") +edb_full_path = pyaedt.downloads.download_file("edb/ANSYS-HSD_V1.aedb", destination=temp_dir.name) +time.sleep(5) + +print(edb_full_path) +# - + +# ### Configure EDB +# +# Create an instance of the ``pyaedt.Edb`` class. + +# + +# Select EDB version (change it manually if needed, e.g. "2023.2") +edb_version = EDB_VERSION +print(f"EDB version: {edb_version}") + +edbapp = pyaedt.Edb(edbpath=edb_full_path, edbversion=edb_version) +# - + +# ### Generate extended nets +# +# An extended net consists of two nets that are connected +# through a passive component such as a resistor or capacitor. + +all_nets = edbapp.extended_nets.auto_identify_signal( + resistor_below=10, inductor_below=1, capacitor_above=1e-9 +) + +# Review the properties of extended nets. + +# + +diff_p = edbapp.nets["PCIe_Gen4_TX3_CAP_P"] +diff_n = edbapp.nets["PCIe_Gen4_TX3_CAP_N"] + +nets_p = list(diff_p.extended_net.nets.keys()) +nets_n = list(diff_n.extended_net.nets.keys()) + +comp_p = list(diff_p.extended_net.components.keys()) +comp_n = list(diff_n.extended_net.components.keys()) + +rlc_p = list(diff_p.extended_net.rlc.keys()) +rlc_n = list(diff_n.extended_net.rlc.keys()) + +print(comp_p, rlc_p, comp_n, rlc_n, sep="\n") +# - + +# Prepare input data for port creation. + +# + +ports = [] +for net_name, net_obj in diff_p.extended_net.nets.items(): + for comp_name, comp_obj in net_obj.components.items(): + if comp_obj.type not in ["Resistor", "Capacitor", "Inductor"]: + ports.append( + { + "port_name": "{}_{}".format(comp_name, net_name), + "comp_name": comp_name, + "net_name": net_name, + } + ) + +for net_name, net_obj in diff_n.extended_net.nets.items(): + for comp_name, comp_obj in net_obj.components.items(): + if comp_obj.type not in ["Resistor", "Capacitor", "Inductor"]: + ports.append( + { + "port_name": "{}_{}".format(comp_name, net_name), + "comp_name": comp_name, + "net_name": net_name, + } + ) + +print(*ports, sep="\n") +# - + +# ### Create ports +# +# Solder balls are generated automatically. The default port type is coax port. + +for d in ports: + port_name = d["port_name"] + comp_name = d["comp_name"] + net_name = d["net_name"] + edbapp.components.create_port_on_component( + component=comp_name, net_list=net_name, port_name=port_name + ) + +# ### Cutout +# +# Retain only relevant parts of the layout. + +nets = [] +nets.extend(nets_p) +nets.extend(nets_n) +edbapp.cutout(signal_list=nets, reference_list=["GND"], extent_type="Bounding") + +# Set up the model for network analysis in SIwave. + +setup = edbapp.create_siwave_syz_setup("setup1") +setup.add_frequency_sweep( + frequency_sweep=[ + ["linear count", "0", "1kHz", 1], + ["log scale", "1kHz", "0.1GHz", 10], + ["linear scale", "0.1GHz", "10GHz", "0.1GHz"], + ] +) + +# Save and close the EDB. + +edbapp.save() +edbapp.close_edb() + +# ### Launch Hfss3dLayout +# +# The HFSS 3D Layout user interface in AEDT is used to import the EDB and +# run the analysis. AEDT 3D Layout can be used to view the model +# if it is launched in graphical mode. + +h3d = pyaedt.Hfss3dLayout( + edb_full_path, + specified_version="2023.2", + non_graphical=False, # Set to true for non-graphical mode. + new_desktop_session=True, +) + +# Define the differential pair. + +h3d.set_differential_pair( + positive_terminal="U1_PCIe_Gen4_TX3_CAP_P", + negative_terminal="U1_PCIe_Gen4_TX3_CAP_N", + diff_name="PAIR_U1", +) +h3d.set_differential_pair( + positive_terminal="X1_PCIe_Gen4_TX3_P", + negative_terminal="X1_PCIe_Gen4_TX3_N", + diff_name="PAIR_X1", +) + +# Solve and plot the results. + +h3d.analyze(num_cores=4) + +# Visualze the results. + +h3d.post.create_report("dB(S(PAIR_U1,PAIR_U1))", context="Differential Pairs") + +# Close AEDT. + +h3d.save_project() +print("Project is saved to {}".format(h3d.project_path)) +h3d.release_desktop(True, True) + +# The following cell cleans up the temporary directory and removes all project data. + +temp_dir.cleanup()