{ "cells": [ { "cell_type": "markdown", "id": "4a4318d5", "metadata": {}, "source": [ "# PCB DCIR analysis\n", "\n", "This example shows how to use PyAEDT to create a design in\n", "Q3D Extractor and run a DC IR drop simulation starting from an EDB project.\n", "\n", "Keywords: **Q3D**, **layout**, **DCIR**." ] }, { "cell_type": "markdown", "id": "5e2e583b", "metadata": {}, "source": [ "## Perform imports and define constants\n", "\n", "Perform required imports." ] }, { "cell_type": "code", "execution_count": null, "id": "8c7af405", "metadata": {}, "outputs": [], "source": [ "import os\n", "import tempfile\n", "import time" ] }, { "cell_type": "code", "execution_count": null, "id": "00198bb1", "metadata": {}, "outputs": [], "source": [ "import ansys.aedt.core\n", "import pyedb" ] }, { "cell_type": "markdown", "id": "acb56102", "metadata": {}, "source": [ "Define constants." ] }, { "cell_type": "code", "execution_count": null, "id": "8ccab75e", "metadata": {}, "outputs": [], "source": [ "AEDT_VERSION = \"2024.2\"\n", "NUM_CORES = 4\n", "NG_MODE = False" ] }, { "cell_type": "markdown", "id": "e9f93ed5", "metadata": {}, "source": [ "## Create temporary directory\n", "\n", "Create a temporary directory where downloaded data or\n", "dumped data can be stored.\n", "If you'd like to retrieve the project data for subsequent use,\n", "the temporary folder name is given by ``temp_folder.name``." ] }, { "cell_type": "code", "execution_count": null, "id": "ee9272d3", "metadata": {}, "outputs": [], "source": [ "temp_folder = tempfile.TemporaryDirectory(suffix=\".ansys\")\n", "# ## Set up project files and path\n", "#\n", "# Download needed project file and set up temporary project directory." ] }, { "cell_type": "code", "execution_count": null, "id": "3f96d80a", "metadata": {}, "outputs": [], "source": [ "aedb_project = ansys.aedt.core.downloads.download_file(\n", " \"edb/ANSYS-HSD_V1.aedb\", destination=temp_folder.name\n", ")\n", "coil = ansys.aedt.core.downloads.download_file(\n", " source=\"inductance_3d_component\",\n", " name=\"air_coil.a3dcomp\",\n", " destination=temp_folder.name,\n", ")\n", "res = ansys.aedt.core.downloads.download_file(\n", " source=\"resistors\", name=\"Res_0402.a3dcomp\", destination=temp_folder.name\n", ")\n", "project_name = \"HSD\"\n", "output_edb = os.path.join(temp_folder.name, project_name + \".aedb\")\n", "output_q3d = os.path.join(temp_folder.name, project_name + \"_q3d.aedt\")" ] }, { "cell_type": "markdown", "id": "5fe41fd0", "metadata": {}, "source": [ "## Open EDB project\n", "\n", "Open the EDB project and create a cutout on the selected nets\n", "before exporting to Q3D." ] }, { "cell_type": "code", "execution_count": null, "id": "3e20bfa2", "metadata": {}, "outputs": [], "source": [ "edb = pyedb.Edb(aedb_project, edbversion=AEDT_VERSION)\n", "signal_nets = [\"1.2V_AVDLL_PLL\", \"1.2V_AVDDL\", \"1.2V_DVDDL\", \"NetR106_1\"]\n", "ground_nets = [\"GND\"]\n", "cutout_points = edb.cutout(\n", " signal_list=signal_nets,\n", " reference_list=ground_nets,\n", " output_aedb_path=output_edb,\n", ")" ] }, { "cell_type": "markdown", "id": "3f536e21", "metadata": {}, "source": [ "## Identify pin positions\n", "\n", "Identify [x,y] pin locations on the components to define where to assign sources\n", "and sinks for Q3D." ] }, { "cell_type": "code", "execution_count": null, "id": "a74ceba1", "metadata": {}, "outputs": [], "source": [ "pin_u11_scl = [\n", " i for i in edb.components[\"U11\"].pins.values() if i.net_name == \"1.2V_AVDLL_PLL\"\n", "]\n", "pin_u9_1 = [i for i in edb.components[\"U9\"].pins.values() if i.net_name == \"1.2V_AVDDL\"]\n", "pin_u9_2 = [i for i in edb.components[\"U9\"].pins.values() if i.net_name == \"1.2V_DVDDL\"]\n", "pin_u11_r106 = [\n", " i for i in edb.components[\"U11\"].pins.values() if i.net_name == \"NetR106_1\"\n", "]" ] }, { "cell_type": "markdown", "id": "4477d1f9", "metadata": {}, "source": [ "## Append Z Positions\n", "\n", "Compute the Q3D 3D position. The units in EDB are meters so the\n", "factor 1000 converts from meters to millimeters." ] }, { "cell_type": "code", "execution_count": null, "id": "febf2586", "metadata": {}, "outputs": [], "source": [ "location_u11_scl = [i * 1000 for i in pin_u11_scl[0].position]\n", "location_u11_scl.append(edb.components[\"U11\"].upper_elevation * 1000)\n", "\n", "location_u9_1_scl = [i * 1000 for i in pin_u9_1[0].position]\n", "location_u9_1_scl.append(edb.components[\"U9\"].upper_elevation * 1000)\n", "\n", "location_u9_2_scl = [i * 1000 for i in pin_u9_2[0].position]\n", "location_u9_2_scl.append(edb.components[\"U9\"].upper_elevation * 1000)\n", "\n", "location_u11_r106 = [i * 1000 for i in pin_u11_r106[0].position]\n", "location_u11_r106.append(edb.components[\"U11\"].upper_elevation * 1000)" ] }, { "cell_type": "markdown", "id": "5ba6bf4e", "metadata": {}, "source": [ "## Identify pin positions for 3D components\n", "\n", "Identify the pin positions where 3D components of passives are to be added." ] }, { "cell_type": "code", "execution_count": null, "id": "8ca8a6ef", "metadata": {}, "outputs": [], "source": [ "location_l2_1 = [i * 1000 for i in edb.components[\"L2\"].pins[\"1\"].position]\n", "location_l2_1.append(edb.components[\"L2\"].upper_elevation * 1000)\n", "location_l4_1 = [i * 1000 for i in edb.components[\"L4\"].pins[\"1\"].position]\n", "location_l4_1.append(edb.components[\"L4\"].upper_elevation * 1000)\n", "\n", "location_r106_1 = [i * 1000 for i in edb.components[\"R106\"].pins[\"1\"].position]\n", "location_r106_1.append(edb.components[\"R106\"].upper_elevation * 1000)" ] }, { "cell_type": "markdown", "id": "69bfc60d", "metadata": {}, "source": [ "## Save and close EDB\n", "\n", "Save and close EDB. Then, open EDT in HFSS 3D Layout to generate the 3D model." ] }, { "cell_type": "code", "execution_count": null, "id": "1abefe46", "metadata": {}, "outputs": [], "source": [ "edb.save_edb()\n", "edb.close_edb()\n", "time.sleep(3)\n", "\n", "h3d = ansys.aedt.core.Hfss3dLayout(\n", " output_edb, version=AEDT_VERSION, non_graphical=NG_MODE, new_desktop=True\n", ")" ] }, { "cell_type": "markdown", "id": "f4b2d807", "metadata": {}, "source": [ "## Export to Q3D\n", "\n", "Create a dummy setup and export the layout to Q3D.\n", "The ``keep_net_name`` parameter reassigns Q3D net names from HFSS 3D Layout." ] }, { "cell_type": "code", "execution_count": null, "id": "38a97aa4", "metadata": {}, "outputs": [], "source": [ "setup = h3d.create_setup()\n", "setup.export_to_q3d(output_q3d, keep_net_name=True)\n", "h3d.close_project()\n", "time.sleep(3)" ] }, { "cell_type": "markdown", "id": "52f5baa1", "metadata": {}, "source": [ "## Open Q3D\n", "\n", "Launch the newly created Q3D project." ] }, { "cell_type": "code", "execution_count": null, "id": "131ff698", "metadata": {}, "outputs": [], "source": [ "q3d = ansys.aedt.core.Q3d(output_q3d, version=AEDT_VERSION)\n", "q3d.modeler.delete(\"GND\")\n", "q3d.modeler.delete(\"16_Bottom_L6\")\n", "q3d.delete_all_nets()" ] }, { "cell_type": "markdown", "id": "15f8cca5", "metadata": {}, "source": [ "## Insert inductors\n", "\n", "Create coordinate systems and place 3D component inductors." ] }, { "cell_type": "code", "execution_count": null, "id": "3943b603", "metadata": {}, "outputs": [], "source": [ "q3d.modeler.create_coordinate_system(location_l2_1, name=\"L2\")\n", "comp = q3d.modeler.insert_3d_component(coil, coordinate_system=\"L2\")\n", "comp.rotate(q3d.AXIS.Z, -90)\n", "comp.parameters[\"n_turns\"] = \"3\"\n", "comp.parameters[\"d_wire\"] = \"100um\"\n", "q3d.modeler.set_working_coordinate_system(\"Global\")\n", "q3d.modeler.create_coordinate_system(location_l4_1, name=\"L4\")\n", "comp2 = q3d.modeler.insert_3d_component(coil, coordinate_system=\"L4\")\n", "comp2.rotate(q3d.AXIS.Z, -90)\n", "comp2.parameters[\"n_turns\"] = \"3\"\n", "comp2.parameters[\"d_wire\"] = \"100um\"\n", "q3d.modeler.set_working_coordinate_system(\"Global\")\n", "\n", "q3d.modeler.set_working_coordinate_system(\"Global\")\n", "q3d.modeler.create_coordinate_system(location_r106_1, name=\"R106\")\n", "comp3 = q3d.modeler.insert_3d_component(\n", " res, geometry_parameters={\"$Resistance\": 2000}, coordinate_system=\"R106\"\n", ")\n", "comp3.rotate(q3d.AXIS.Z, -90)\n", "\n", "q3d.modeler.set_working_coordinate_system(\"Global\")" ] }, { "cell_type": "markdown", "id": "08a0d15e", "metadata": {}, "source": [ "## Delete dielectrics\n", "\n", "Delete all dielectric objects since they are not needed in DC analysis." ] }, { "cell_type": "code", "execution_count": null, "id": "22176c06", "metadata": {}, "outputs": [], "source": [ "q3d.modeler.delete(q3d.modeler.get_objects_by_material(\"Megtron4\"))\n", "q3d.modeler.delete(q3d.modeler.get_objects_by_material(\"Megtron4_2\"))\n", "q3d.modeler.delete(q3d.modeler.get_objects_by_material(\"Megtron4_3\"))\n", "q3d.modeler.delete(q3d.modeler.get_objects_by_material(\"Solder Resist\"))\n", "\n", "objs_copper = q3d.modeler.get_objects_by_material(\"copper\")\n", "objs_copper_names = [i.name for i in objs_copper]\n", "q3d.plot(\n", " show=False,\n", " assignment=objs_copper_names,\n", " plot_as_separate_objects=False,\n", " output_file=os.path.join(temp_folder.name, \"Q3D.jpg\"),\n", " plot_air_objects=False,\n", ")" ] }, { "cell_type": "markdown", "id": "e60b17b4", "metadata": {}, "source": [ "## Assign source and sink\n", "\n", "Use previously calculated positions to identify faces. Select the net ``1_Top`` and\n", "assign sources and sinks on nets." ] }, { "cell_type": "code", "execution_count": null, "id": "695d4d88", "metadata": {}, "outputs": [], "source": [ "sink_f = q3d.modeler.create_circle(q3d.PLANE.XY, location_u11_scl, 0.1)\n", "source_f1 = q3d.modeler.create_circle(q3d.PLANE.XY, location_u9_1_scl, 0.1)\n", "source_f2 = q3d.modeler.create_circle(q3d.PLANE.XY, location_u9_2_scl, 0.1)\n", "source_f3 = q3d.modeler.create_circle(q3d.PLANE.XY, location_u11_r106, 0.1)\n", "sources_objs = [source_f1, source_f2, source_f3]\n", "\n", "q3d.auto_identify_nets()\n", "\n", "identified_net = q3d.nets[0]\n", "\n", "q3d.sink(sink_f, net_name=identified_net)\n", "\n", "source1 = q3d.source(source_f1, net_name=identified_net)\n", "\n", "source2 = q3d.source(source_f2, net_name=identified_net)\n", "source3 = q3d.source(source_f3, net_name=identified_net)\n", "sources_bounds = [source1, source2, source3]\n", "\n", "q3d.edit_sources(\n", " dcrl={\n", " \"{}:{}\".format(source1.props[\"Net\"], source1.name): \"-1.0A\",\n", " \"{}:{}\".format(source2.props[\"Net\"], source2.name): \"-1.0A\",\n", " \"{}:{}\".format(source2.props[\"Net\"], source3.name): \"-1.0A\",\n", " }\n", ")" ] }, { "cell_type": "markdown", "id": "535a8c06", "metadata": {}, "source": [ "## Create setup\n", "\n", "Create a setup and a frequency sweep from DC to 2GHz.\n", "Then, analyze the project." ] }, { "cell_type": "code", "execution_count": null, "id": "aaad4e48", "metadata": {}, "outputs": [], "source": [ "setup = q3d.create_setup()\n", "setup.dc_enabled = True\n", "setup.capacitance_enabled = False\n", "setup.ac_rl_enabled = False\n", "setup.props[\"SaveFields\"] = True\n", "setup.props[\"DC\"][\"Cond\"][\"MaxPass\"] = 3\n", "setup.analyze()" ] }, { "cell_type": "markdown", "id": "afd2c7e6", "metadata": {}, "source": [ "## Solve setup" ] }, { "cell_type": "code", "execution_count": null, "id": "467256a4", "metadata": {}, "outputs": [], "source": [ "q3d.save_project()\n", "q3d.analyze_setup(setup.name, cores=NUM_CORES)" ] }, { "cell_type": "markdown", "id": "92e2379d", "metadata": {}, "source": [ "## Create a named expression\n", "\n", "Use PyAEDT advanced fields calculator to add from the expressions catalog the voltage drop." ] }, { "cell_type": "code", "execution_count": null, "id": "2a557fb3", "metadata": {}, "outputs": [], "source": [ "voltage_drop = q3d.post.fields_calculator.add_expression(\"voltage_drop\", None)" ] }, { "cell_type": "markdown", "id": "f8169d5f", "metadata": {}, "source": [ "## Create Phi plot\n", "\n", "Compute ACL solutions and plot them." ] }, { "cell_type": "code", "execution_count": null, "id": "7295e2c4", "metadata": { "lines_to_next_cell": 2 }, "outputs": [], "source": [ "plot1 = q3d.post.create_fieldplot_surface(\n", " q3d.modeler.get_objects_by_material(\"copper\"),\n", " quantity=voltage_drop,\n", " intrinsics={\"Freq\": \"1GHz\"},\n", ")\n", "\n", "q3d.post.plot_field_from_fieldplot(\n", " plot1.name,\n", " project_path=temp_folder.name,\n", " mesh_plot=False,\n", " image_format=\"jpg\",\n", " view=\"isometric\",\n", " show=False,\n", " plot_cad_objs=False,\n", " log_scale=False,\n", ")" ] }, { "cell_type": "markdown", "id": "a6b3a13b", "metadata": {}, "source": [ "## Compute voltage on source circles\n", "\n", "Use PyAEDT advanced field calculator to compute the voltage on source circles and get the value\n", "using the ``get_solution_data()`` method." ] }, { "cell_type": "code", "execution_count": null, "id": "b0442d79", "metadata": {}, "outputs": [], "source": [ "v_surface = {\n", " \"name\": \"\",\n", " \"description\": \"Maximum value of voltage on a surface\",\n", " \"design_type\": [\"Q3D Extractor\"],\n", " \"fields_type\": [\"DC R/L Fields\"],\n", " \"primary_sweep\": \"Freq\",\n", " \"assignment\": \"\",\n", " \"assignment_type\": [\"Face\", \"Sheet\"],\n", " \"operations\": [\n", " f\"NameOfExpression({voltage_drop})\",\n", " \"EnterSurface('assignment')\",\n", " \"Operation('SurfaceValue')\",\n", " \"Operation('Maximum')\",\n", " ],\n", " \"report\": [\"Field_3D\"],\n", "}\n", "for source_circle, source_bound in zip(sources_objs, sources_bounds):\n", " v_surface[\"name\"] = \"V{}\".format(source_bound.name)\n", " q3d.post.fields_calculator.add_expression(v_surface, source_circle.name)\n", "\n", " data = q3d.post.get_solution_data(\n", " \"V{}\".format(source_bound.name),\n", " q3d.nominal_adaptive,\n", " variations={\"Freq\": \"1GHz\"},\n", " report_category=\"DC R/L Fields\",\n", " )\n", " if data:\n", " print(data.data_real(\"V{}\".format(source_bound.name)))" ] }, { "cell_type": "markdown", "id": "c2892317", "metadata": {}, "source": [ "## Release AEDT" ] }, { "cell_type": "code", "execution_count": null, "id": "baaa0bdf", "metadata": {}, "outputs": [], "source": [ "q3d.save_project()\n", "q3d.release_desktop()\n", "# Wait 3 seconds to allow AEDT to shut down before cleaning the temporary directory.\n", "time.sleep(3)" ] }, { "cell_type": "markdown", "id": "2ee3ef3e", "metadata": {}, "source": [ "## Clean up\n", "\n", "All project files are saved in the folder ``temp_folder.name``.\n", "If you've run this example as a Jupyter notebook, you\n", "can retrieve those project files. The following cell\n", "removes all temporary files, including the project folder." ] }, { "cell_type": "code", "execution_count": null, "id": "05e98154", "metadata": {}, "outputs": [], "source": [ "temp_folder.cleanup()" ] } ], "metadata": { "jupytext": { "cell_metadata_filter": "-all", "main_language": "python", "notebook_metadata_filter": "-all" } }, "nbformat": 4, "nbformat_minor": 5 }