From 09db4b3907850d635a0be750abb07bcd44235422 Mon Sep 17 00:00:00 2001 From: svandenb-dev <74993647+svandenb-dev@users.noreply.github.com> Date: Fri, 24 Nov 2023 13:29:10 +0100 Subject: [PATCH] Solder balls height control with simconfig (#3905) * edb intersection bug fix * custom solder balls hieght support with simulation configuration class * arbitrary size and shape support added * bug fix * codecov fix --------- Co-authored-by: Samuel Lopez <85613111+Samuelopez-ansys@users.noreply.github.com> --- .../TEDB/ANSYS-HSD_V1.aedb/simsetup.json | 148 +++++++++++++++++ .../simsetup_custom_sballs.json | 156 ++++++++++++++++++ _unittest/test_00_EDB.py | 13 ++ pyaedt/edb.py | 62 +++++-- pyaedt/edb_core/components.py | 57 ++++++- pyaedt/edb_core/hfss.py | 17 +- 6 files changed, 424 insertions(+), 29 deletions(-) create mode 100644 _unittest/example_models/TEDB/ANSYS-HSD_V1.aedb/simsetup.json create mode 100644 _unittest/example_models/TEDB/ANSYS-HSD_V1.aedb/simsetup_custom_sballs.json diff --git a/_unittest/example_models/TEDB/ANSYS-HSD_V1.aedb/simsetup.json b/_unittest/example_models/TEDB/ANSYS-HSD_V1.aedb/simsetup.json new file mode 100644 index 00000000000..7d9b5448660 --- /dev/null +++ b/_unittest/example_models/TEDB/ANSYS-HSD_V1.aedb/simsetup.json @@ -0,0 +1,148 @@ +{ + "filename": null, + "open_edb_after_build": true, + "dc_settings": { + "dc_compute_inductance": false, + "dc_contact_radius": "100um", + "dc_slide_position": 1, + "dc_use_dc_custom_settings": false, + "dc_plot_jv": true, + "dc_min_plane_area_to_mesh": "8mil2", + "dc_min_void_area_to_mesh": "0.734mil2", + "dc_error_energy": 0.02, + "dc_max_init_mesh_edge_length": "5.0mm", + "dc_max_num_pass": 5, + "dc_min_num_pass": 1, + "dc_mesh_bondwires": true, + "dc_num_bondwire_sides": 8, + "dc_mesh_vias": true, + "dc_num_via_sides": 8, + "dc_percent_local_refinement": 0.2, + "dc_perform_adaptive_refinement": true, + "dc_refine_bondwires": true, + "dc_refine_vias": true, + "dc_report_config_file": "", + "dc_report_show_Active_devices": true, + "dc_export_thermal_data": true, + "dc_full_report_path": "", + "dc_icepak_temp_file": "", + "dc_import_thermal_data": false, + "dc_per_pin_res_path": "", + "dc_per_pin_use_pin_format": true, + "dc_use_loop_res_for_per_pin": true, + "dc_via_report_path": "", + "dc_source_terms_to_ground": {} + }, + "ac_settings": { + "sweep_interpolating": true, + "use_q3d_for_dc": false, + "relative_error": 0.005, + "use_error_z0": false, + "percentage_error_z0": 1, + "enforce_causality": true, + "enforce_passivity": false, + "passivity_tolerance": 0.0001, + "sweep_name": "Sweep1", + "radiation_box": 2, + "start_freq": "0.0GHz", + "stop_freq": "20GHz", + "sweep_type": 0, + "step_freq": "0GHz", + "decade_count": 100, + "mesh_freq": "33GHz", + "max_num_passes": 30, + "max_mag_delta_s": 0.03, + "min_num_passes": 1, + "basis_order": 0, + "do_lambda_refinement": true, + "arc_angle": "10deg", + "start_azimuth": 5, + "max_arc_points": 24, + "use_arc_to_chord_error": true, + "arc_to_chord_error": "1um", + "defeature_abs_length": "1um", + "defeature_layout": true, + "minimum_void_surface": 0, + "max_suf_dev": 0.001, + "process_padstack_definitions": false, + "return_current_distribution": true, + "ignore_non_functional_pads": true, + "include_inter_plane_coupling": true, + "xtalk_threshold": -50, + "min_void_area": "0.01mm2", + "min_pad_area_to_mesh": "0.01mm2", + "snap_length_threshold": "2.5um", + "min_plane_area_to_mesh": "4mil2", + "mesh_sizefactor": 0.0 + }, + "batch_solve_settings": { + "signal_nets": [ + "PCIe_Gen4_RX0_N", + "PCIe_Gen4_RX0_P", + "PCIe_Gen4_RX1_N", + "PCIe_Gen4_RX1_P", + "PCIe_Gen4_RX2_N", + "PCIe_Gen4_RX2_P", + "PCIe_Gen4_RX3_N", + "PCIe_Gen4_RX3_P", + "PCIe_Gen4_TX0_N", + "PCIe_Gen4_TX0_CAP_N", + "PCIe_Gen4_TX0_p", + "PCIe_Gen4_TX0_CAP_P", + "PCIe_Gen4_TX1_N", + "PCIe_Gen4_TX1_CAP_N", + "PCIe_Gen4_TX1_P", + "PCIe_Gen4_TX1_CAP_P", + "PCIe_Gen4_TX2_N", + "PCIe_Gen4_TX2_CAP_N", + "PCIe_Gen4_TX2_P", + "PCIe_Gen4_TX2_CAP_P", + "PCIe_Gen4_TX3_N", + "PCIe_Gen4_TX3_CAP_N", + "PCIe_Gen4_TX3_P", + "PCIe_Gen4_TX3_CAP_P" + ], + "power_nets": [ + "1V0", + "2V5", + "5V", + "GND" + ], + "components": [ + "X1", + "U1" + ], + "cutout_subdesign_type": 1, + "cutout_subdesign_expansion": 0.001, + "cutout_subdesign_round_corner": true, + "use_default_cutout": false, + "generate_excitations": true, + "add_frequency_sweep": true, + "include_only_selected_nets": false, + "generate_solder_balls": true, + "coax_solder_ball_diameter": [], + "use_default_coax_port_radial_extension": true, + "trim_reference_size": false, + "output_aedb": null, + "signal_layers_properties": {}, + "coplanar_instances": [], + "signal_layer_etching_instances": [], + "etching_factor_instances": [], + "use_dielectric_extent_multiple": true, + "dielectric_extent": 0.001, + "use_airbox_horizontal_multiple": true, + "airbox_horizontal_extent": 0.1, + "use_airbox_negative_vertical_extent_multiple": true, + "airbox_negative_vertical_extent": 0.1, + "use_airbox_positive_vertical_extent_multiple": true, + "airbox_positive_vertical_extent": 0.1, + "honor_user_dielectric": false, + "truncate_airbox_at_ground": false, + "use_radiation_boundary": true, + "do_cutout_subdesign": true, + "do_pin_group": true, + "sources": [] + }, + "setup_name": "Pyaedt_setup", + "solver_type": 6 +} \ No newline at end of file diff --git a/_unittest/example_models/TEDB/ANSYS-HSD_V1.aedb/simsetup_custom_sballs.json b/_unittest/example_models/TEDB/ANSYS-HSD_V1.aedb/simsetup_custom_sballs.json new file mode 100644 index 00000000000..ba5ccd3273b --- /dev/null +++ b/_unittest/example_models/TEDB/ANSYS-HSD_V1.aedb/simsetup_custom_sballs.json @@ -0,0 +1,156 @@ +{ + "filename": null, + "open_edb_after_build": true, + "dc_settings": { + "dc_compute_inductance": false, + "dc_contact_radius": "100um", + "dc_slide_position": 1, + "dc_use_dc_custom_settings": false, + "dc_plot_jv": true, + "dc_min_plane_area_to_mesh": "8mil2", + "dc_min_void_area_to_mesh": "0.734mil2", + "dc_error_energy": 0.02, + "dc_max_init_mesh_edge_length": "5.0mm", + "dc_max_num_pass": 5, + "dc_min_num_pass": 1, + "dc_mesh_bondwires": true, + "dc_num_bondwire_sides": 8, + "dc_mesh_vias": true, + "dc_num_via_sides": 8, + "dc_percent_local_refinement": 0.2, + "dc_perform_adaptive_refinement": true, + "dc_refine_bondwires": true, + "dc_refine_vias": true, + "dc_report_config_file": "", + "dc_report_show_Active_devices": true, + "dc_export_thermal_data": true, + "dc_full_report_path": "", + "dc_icepak_temp_file": "", + "dc_import_thermal_data": false, + "dc_per_pin_res_path": "", + "dc_per_pin_use_pin_format": true, + "dc_use_loop_res_for_per_pin": true, + "dc_via_report_path": "", + "dc_source_terms_to_ground": {} + }, + "ac_settings": { + "sweep_interpolating": true, + "use_q3d_for_dc": false, + "relative_error": 0.005, + "use_error_z0": false, + "percentage_error_z0": 1, + "enforce_causality": true, + "enforce_passivity": false, + "passivity_tolerance": 0.0001, + "sweep_name": "Sweep1", + "radiation_box": 2, + "start_freq": "0.0GHz", + "stop_freq": "20GHz", + "sweep_type": 0, + "step_freq": "0GHz", + "decade_count": 100, + "mesh_freq": "33GHz", + "max_num_passes": 30, + "max_mag_delta_s": 0.03, + "min_num_passes": 1, + "basis_order": 0, + "do_lambda_refinement": true, + "arc_angle": "10deg", + "start_azimuth": 5, + "max_arc_points": 24, + "use_arc_to_chord_error": true, + "arc_to_chord_error": "1um", + "defeature_abs_length": "1um", + "defeature_layout": true, + "minimum_void_surface": 0, + "max_suf_dev": 0.001, + "process_padstack_definitions": false, + "return_current_distribution": true, + "ignore_non_functional_pads": true, + "include_inter_plane_coupling": true, + "xtalk_threshold": -50, + "min_void_area": "0.01mm2", + "min_pad_area_to_mesh": "0.01mm2", + "snap_length_threshold": "2.5um", + "min_plane_area_to_mesh": "4mil2", + "mesh_sizefactor": 0.0 + }, + "batch_solve_settings": { + "signal_nets": [ + "PCIe_Gen4_RX0_N", + "PCIe_Gen4_RX0_P", + "PCIe_Gen4_RX1_N", + "PCIe_Gen4_RX1_P", + "PCIe_Gen4_RX2_N", + "PCIe_Gen4_RX2_P", + "PCIe_Gen4_RX3_N", + "PCIe_Gen4_RX3_P", + "PCIe_Gen4_TX0_N", + "PCIe_Gen4_TX0_CAP_N", + "PCIe_Gen4_TX0_p", + "PCIe_Gen4_TX0_CAP_P", + "PCIe_Gen4_TX1_N", + "PCIe_Gen4_TX1_CAP_N", + "PCIe_Gen4_TX1_P", + "PCIe_Gen4_TX1_CAP_P", + "PCIe_Gen4_TX2_N", + "PCIe_Gen4_TX2_CAP_N", + "PCIe_Gen4_TX2_P", + "PCIe_Gen4_TX2_CAP_P", + "PCIe_Gen4_TX3_N", + "PCIe_Gen4_TX3_CAP_N", + "PCIe_Gen4_TX3_P", + "PCIe_Gen4_TX3_CAP_P" + ], + "power_nets": [ + "1V0", + "2V5", + "5V", + "GND" + ], + "components": [ + { + "refdes": "X1", + "solder_balls_height": 0.00025, + "solder_balls_size": 0.0001, + "solder_balls_mid_size": 0.00015 + }, + { + "refdes": "U1", + "solder_balls_height": 0.00035 + } + ], + "cutout_subdesign_type": 1, + "cutout_subdesign_expansion": 0.001, + "cutout_subdesign_round_corner": true, + "use_default_cutout": false, + "generate_excitations": true, + "add_frequency_sweep": true, + "include_only_selected_nets": false, + "generate_solder_balls": true, + "coax_solder_ball_diameter": [], + "use_default_coax_port_radial_extension": true, + "trim_reference_size": false, + "output_aedb": null, + "signal_layers_properties": {}, + "coplanar_instances": [], + "signal_layer_etching_instances": [], + "etching_factor_instances": [], + "use_dielectric_extent_multiple": true, + "dielectric_extent": 0.001, + "use_airbox_horizontal_multiple": true, + "airbox_horizontal_extent": 0.1, + "use_airbox_negative_vertical_extent_multiple": true, + "airbox_negative_vertical_extent": 0.1, + "use_airbox_positive_vertical_extent_multiple": true, + "airbox_positive_vertical_extent": 0.1, + "honor_user_dielectric": false, + "truncate_airbox_at_ground": false, + "use_radiation_boundary": true, + "do_cutout_subdesign": true, + "do_pin_group": true, + "sources": [] + }, + "setup_name": "Pyaedt_setup", + "solver_type": 6 +} \ No newline at end of file diff --git a/_unittest/test_00_EDB.py b/_unittest/test_00_EDB.py index 4b496e0e28e..32b3a9f6949 100644 --- a/_unittest/test_00_EDB.py +++ b/_unittest/test_00_EDB.py @@ -2980,3 +2980,16 @@ def test_151_materials_read_materials(self): assert mats[key]["mass_density"] == 8055 assert mats[key]["specific_heat"] == 480 assert mats[key]["thermal_expansion_coeffcient"] == 1.08e-005 + + def test_152_simconfig_built_custom_sballs_height(self): + source_path = os.path.join(local_path, "example_models", test_subfolder, "ANSYS-HSD_V1.aedb") + target_path = os.path.join(self.local_scratch.path, "test_custom_sball_height", "ANSYS-HSD_V1.aedb") + self.local_scratch.copyfolder(source_path, target_path) + json_file = os.path.join(target_path, "simsetup_custom_sballs.json") + edbapp = Edb(target_path, edbversion=desktop_version) + simconfig = edbapp.new_simulation_configuration() + simconfig.import_json(json_file) + edbapp.build_simulation_project(simconfig) + assert round(edbapp.components["X1"].solder_ball_height, 6) == 0.00025 + assert round(edbapp.components["U1"].solder_ball_height, 6) == 0.00035 + edbapp.close_edb() diff --git a/pyaedt/edb.py b/pyaedt/edb.py index d3768d224e1..c33c6f86bee 100644 --- a/pyaedt/edb.py +++ b/pyaedt/edb.py @@ -3098,10 +3098,9 @@ def build_simulation_project(self, simulation_setup): idx = simulation_setup.signal_layer_etching_instances.index(layer) if len(simulation_setup.etching_factor_instances) > idx: self.stackup[layer].etch_factor = float(simulation_setup.etching_factor_instances[idx]) - if not simulation_setup.signal_nets and simulation_setup.components: nets_to_include = [] - pnets = list(self.nets.power_nets.keys())[:] + pnets = list(self.nets.power.keys())[:] for el in simulation_setup.components: nets_to_include.append([i for i in self.components[el].nets if i not in pnets]) simulation_setup.signal_nets = [ @@ -3164,13 +3163,33 @@ def build_simulation_project(self, simulation_setup): if not simulation_setup.generate_solder_balls: source_type = SourceType.CircPort for cmp in simulation_setup.components: - self.components.create_port_on_component( - cmp, - net_list=simulation_setup.signal_nets, - do_pingroup=False, - reference_net=simulation_setup.power_nets, - port_type=source_type, - ) + if isinstance(cmp, str): # keep legacy component + self.components.create_port_on_component( + cmp, + net_list=simulation_setup.signal_nets, + do_pingroup=False, + reference_net=simulation_setup.power_nets, + port_type=source_type, + ) + elif isinstance(cmp, dict): + if "refdes" in cmp: + if not "solder_balls_height" in cmp: # pragma no cover + cmp["solder_balls_height"] = None + if not "solder_balls_size" in cmp: # pragma no cover + cmp["solder_balls_size"] = None + cmp["solder_balls_mid_size"] = None + if not "solder_balls_mid_size" in cmp: # pragma no cover + cmp["solder_balls_mid_size"] = None + self.components.create_port_on_component( + cmp["refdes"], + net_list=simulation_setup.signal_nets, + do_pingroup=False, + reference_net=simulation_setup.power_nets, + port_type=source_type, + solder_balls_height=cmp["solder_balls_height"], + solder_balls_size=cmp["solder_balls_size"], + solder_balls_mid_size=cmp["solder_balls_mid_size"], + ) if simulation_setup.generate_solder_balls and not self.hfss.set_coax_port_attributes( simulation_setup ): # pragma: no cover @@ -3192,17 +3211,26 @@ def build_simulation_project(self, simulation_setup): if simulation_setup.solver_type == SolverType.SiwaveSYZ: if simulation_setup.generate_excitations: for cmp in simulation_setup.components: - self.components.create_port_on_component( - cmp, - net_list=simulation_setup.signal_nets, - do_pingroup=simulation_setup.do_pingroup, - reference_net=simulation_setup.power_nets, - port_type=SourceType.CircPort, - ) + if isinstance(cmp, str): # keep legacy + self.components.create_port_on_component( + cmp, + net_list=simulation_setup.signal_nets, + do_pingroup=simulation_setup.do_pingroup, + reference_net=simulation_setup.power_nets, + port_type=SourceType.CircPort, + ) + elif isinstance(cmp, dict): + if "refdes" in cmp: # pragma no cover + self.components.create_port_on_component( + cmp["refdes"], + net_list=simulation_setup.signal_nets, + do_pingroup=simulation_setup.do_pingroup, + reference_net=simulation_setup.power_nets, + port_type=SourceType.CircPort, + ) self.logger.info("Configuring analysis setup.") if not self.siwave.configure_siw_analysis_setup(simulation_setup): # pragma: no cover self.logger.error("Failed to configure Siwave simulation setup.") - if simulation_setup.solver_type == SolverType.SiwaveDC: if simulation_setup.generate_excitations: self.components.create_source_on_component(simulation_setup.sources) diff --git a/pyaedt/edb_core/components.py b/pyaedt/edb_core/components.py index 48d43bfdb49..92fb5db470e 100644 --- a/pyaedt/edb_core/components.py +++ b/pyaedt/edb_core/components.py @@ -805,7 +805,16 @@ def create_port_on_pins(self, refdes, pins, reference_pins, impedance=50.0, port @pyaedt_function_handler() def create_port_on_component( - self, component, net_list, port_type=SourceType.CoaxPort, do_pingroup=True, reference_net="gnd", port_name=None + self, + component, + net_list, + port_type=SourceType.CoaxPort, + do_pingroup=True, + reference_net="gnd", + port_name=None, + solder_balls_height=None, + solder_balls_size=None, + solder_balls_mid_size=None, ): """Create ports on a component. @@ -830,7 +839,14 @@ def create_port_on_component( If a port with the specified name already exists, the default naming convention is used so that port creation does not fail. - + solder_balls_height : float, optional + Solder balls height used for the component. When provided default value is overwritten and must be + provided in meter. + solder_balls_size : float, optional + Solder balls diameter. When provided auto evaluation based on padstack size will be disabled. + solder_balls_mid_size : float, optional + Solder balls mid diameter. When provided if value is different than solder balls size, spheroid shape will + be switched. Returns ------- double, bool @@ -875,13 +891,36 @@ def create_port_on_component( if port_type == SourceType.CoaxPort: pad_params = self._padstack.get_pad_parameters(pin=cmp_pins[0], layername=pin_layers[0], pad_type=0) if not pad_params[0] == 7: - sball_diam = min([self._pedb.edb_value(val).ToDouble() for val in pad_params[1]]) - solder_ball_height = 2 * sball_diam / 3 - else: - bbox = pad_params[1] - sball_diam = min([abs(bbox[2] - bbox[0]), abs(bbox[3] - bbox[1])]) * 0.8 - solder_ball_height = 2 * sball_diam / 3 - self.set_solder_ball(component, solder_ball_height, sball_diam) + if not solder_balls_size: # pragma no cover + sball_diam = min([self._pedb.edb_value(val).ToDouble() for val in pad_params[1]]) + sball_mid_diam = sball_diam + else: # pragma no cover + sball_diam = solder_balls_size + if solder_balls_mid_size: + sball_mid_diam = solder_balls_mid_size + else: + sball_mid_diam = solder_balls_size + if not solder_balls_height: # pragma no cover + solder_balls_height = 2 * sball_diam / 3 + else: # pragma no cover + if not solder_balls_size: + bbox = pad_params[1] + sball_diam = min([abs(bbox[2] - bbox[0]), abs(bbox[3] - bbox[1])]) * 0.8 + else: + if not solder_balls_mid_size: + sball_mid_diam = solder_balls_size + if not solder_balls_height: + solder_balls_height = 2 * sball_diam / 3 + sball_shape = "Cylinder" + if not sball_diam == sball_mid_diam: + sball_shape = "Spheroid" + self.set_solder_ball( + component=component, + sball_height=solder_balls_height, + sball_diam=sball_diam, + sball_mid_diam=sball_mid_diam, + shape=sball_shape, + ) for pin in cmp_pins: self._padstack.create_coax_port(padstackinstance=pin, name=port_name) diff --git a/pyaedt/edb_core/hfss.py b/pyaedt/edb_core/hfss.py index d202d05db19..81cbf6242b2 100644 --- a/pyaedt/edb_core/hfss.py +++ b/pyaedt/edb_core/hfss.py @@ -1427,9 +1427,20 @@ def set_coax_port_attributes(self, simulation_setup=None): ) return False net_names = [net.name for net in self._layout.nets if not net.IsPowerGround()] - cmp_names = ( - simulation_setup.components if simulation_setup.components else [gg.GetName() for gg in self._layout.groups] - ) + if simulation_setup.components and isinstance(simulation_setup.components[0], str): + cmp_names = ( + simulation_setup.components + if simulation_setup.components + else [gg.GetName() for gg in self._layout.groups] + ) + elif ( + simulation_setup.components + and isinstance(simulation_setup.components[0], dict) + and "refdes" in simulation_setup.components[0] + ): + cmp_names = [cmp["refdes"] for cmp in simulation_setup.components] + else: + cmp_names = [] ii = 0 for cc in cmp_names: cmp = self._pedb.edb_api.cell.hierarchy.component.FindByName(self._active_layout, cc)