pyedb 0.58.0__py3-none-any.whl → 0.60.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyedb might be problematic. Click here for more details.
- pyedb/__init__.py +23 -1
- pyedb/common/__init__.py +21 -0
- pyedb/common/nets.py +22 -0
- pyedb/component_libraries/ansys_components.py +22 -0
- pyedb/configuration/__init__.py +21 -0
- pyedb/configuration/cfg_boundaries.py +1 -1
- pyedb/configuration/cfg_common.py +1 -1
- pyedb/configuration/cfg_components.py +1 -1
- pyedb/configuration/cfg_data.py +1 -1
- pyedb/configuration/cfg_general.py +1 -1
- pyedb/configuration/cfg_modeler.py +1 -1
- pyedb/configuration/cfg_nets.py +1 -1
- pyedb/configuration/cfg_operations.py +1 -1
- pyedb/configuration/cfg_package_definition.py +1 -1
- pyedb/configuration/cfg_padstacks.py +1 -1
- pyedb/configuration/cfg_pin_groups.py +1 -1
- pyedb/configuration/cfg_ports_sources.py +3 -2
- pyedb/configuration/cfg_s_parameter_models.py +1 -1
- pyedb/configuration/cfg_setup.py +5 -1
- pyedb/configuration/cfg_spice_models.py +1 -1
- pyedb/configuration/cfg_stackup.py +1 -1
- pyedb/configuration/cfg_terminals.py +22 -0
- pyedb/configuration/configuration.py +6 -5
- pyedb/dotnet/__init__.py +21 -0
- pyedb/dotnet/clr_module.py +22 -0
- pyedb/dotnet/database/Variables.py +1 -1
- pyedb/dotnet/database/__init__.py +22 -0
- pyedb/dotnet/database/cell/__init__.py +21 -0
- pyedb/dotnet/database/cell/connectable.py +1 -1
- pyedb/dotnet/database/cell/hierarchy/__init__.py +21 -0
- pyedb/dotnet/database/cell/hierarchy/component.py +1 -1
- pyedb/dotnet/database/cell/hierarchy/hierarchy_obj.py +1 -1
- pyedb/dotnet/database/cell/hierarchy/model.py +1 -1
- pyedb/dotnet/database/cell/hierarchy/netlist_model.py +1 -1
- pyedb/dotnet/database/cell/hierarchy/pin_pair_model.py +1 -1
- pyedb/dotnet/database/cell/hierarchy/s_parameter_model.py +1 -1
- pyedb/dotnet/database/cell/hierarchy/spice_model.py +1 -1
- pyedb/dotnet/database/cell/layout.py +5 -4
- pyedb/dotnet/database/cell/layout_obj.py +1 -1
- pyedb/dotnet/database/cell/primitive/__init__.py +22 -0
- pyedb/dotnet/database/cell/primitive/bondwire.py +1 -1
- pyedb/dotnet/database/cell/primitive/path.py +1 -1
- pyedb/dotnet/database/cell/primitive/primitive.py +1 -1
- pyedb/dotnet/database/cell/terminal/__init__.py +21 -0
- pyedb/dotnet/database/cell/terminal/bundle_terminal.py +1 -1
- pyedb/dotnet/database/cell/terminal/edge_terminal.py +1 -1
- pyedb/dotnet/database/cell/terminal/padstack_instance_terminal.py +1 -1
- pyedb/dotnet/database/cell/terminal/pingroup_terminal.py +1 -1
- pyedb/dotnet/database/cell/terminal/point_terminal.py +1 -1
- pyedb/dotnet/database/cell/terminal/terminal.py +7 -2
- pyedb/dotnet/database/cell/voltage_regulator.py +1 -1
- pyedb/dotnet/database/components.py +38 -40
- pyedb/dotnet/database/definition/__init__.py +21 -0
- pyedb/dotnet/database/definition/component_def.py +1 -1
- pyedb/dotnet/database/definition/component_model.py +1 -1
- pyedb/dotnet/database/definition/definition_obj.py +1 -1
- pyedb/dotnet/database/definition/definitions.py +1 -1
- pyedb/dotnet/database/definition/package_def.py +1 -1
- pyedb/dotnet/database/dotnet/__init__.py +21 -0
- pyedb/dotnet/database/dotnet/database.py +1 -1
- pyedb/dotnet/database/dotnet/primitive.py +1 -1
- pyedb/dotnet/database/edb_data/__init__.py +21 -0
- pyedb/dotnet/database/edb_data/control_file.py +1 -1
- pyedb/dotnet/database/edb_data/design_options.py +1 -1
- pyedb/dotnet/database/edb_data/edbvalue.py +1 -1
- pyedb/dotnet/database/edb_data/hfss_extent_info.py +1 -1
- pyedb/dotnet/database/edb_data/layer_data.py +1 -1
- pyedb/dotnet/database/edb_data/nets_data.py +1 -1
- pyedb/dotnet/database/edb_data/padstacks_data.py +1 -1
- pyedb/dotnet/database/edb_data/ports.py +1 -1
- pyedb/dotnet/database/edb_data/primitives_data.py +1 -1
- pyedb/dotnet/database/edb_data/raptor_x_simulation_setup_data.py +1 -1
- pyedb/dotnet/database/edb_data/simulation_configuration.py +1 -1
- pyedb/dotnet/database/edb_data/sources.py +1 -1
- pyedb/dotnet/database/edb_data/utilities.py +1 -1
- pyedb/dotnet/database/edb_data/variables.py +1 -1
- pyedb/dotnet/database/general.py +4 -3
- pyedb/dotnet/database/geometry/__init__.py +21 -0
- pyedb/dotnet/database/geometry/point_data.py +1 -1
- pyedb/dotnet/database/geometry/polygon_data.py +1 -1
- pyedb/dotnet/database/hfss.py +1 -1
- pyedb/dotnet/database/layout_obj_instance.py +1 -1
- pyedb/dotnet/database/layout_validation.py +1 -1
- pyedb/dotnet/database/materials.py +1 -1
- pyedb/dotnet/database/modeler.py +1 -1
- pyedb/dotnet/database/net_class.py +1 -1
- pyedb/dotnet/database/nets.py +1 -1
- pyedb/dotnet/database/padstack.py +1 -1
- pyedb/dotnet/database/sim_setup_data/__init__.py +22 -0
- pyedb/dotnet/database/sim_setup_data/data/__init__.py +22 -0
- pyedb/dotnet/database/sim_setup_data/data/adaptive_frequency_data.py +1 -1
- pyedb/dotnet/database/sim_setup_data/data/mesh_operation.py +1 -1
- pyedb/dotnet/database/sim_setup_data/data/settings.py +3 -3
- pyedb/dotnet/database/sim_setup_data/data/sim_setup_info.py +1 -1
- pyedb/dotnet/database/sim_setup_data/data/simulation_settings.py +1 -1
- pyedb/dotnet/database/sim_setup_data/data/siw_dc_ir_settings.py +1 -1
- pyedb/dotnet/database/sim_setup_data/data/sweep_data.py +1 -1
- pyedb/dotnet/database/sim_setup_data/io/__init__.py +21 -0
- pyedb/dotnet/database/sim_setup_data/io/siwave.py +1 -1
- pyedb/dotnet/database/siwave.py +1 -1
- pyedb/dotnet/database/stackup.py +1 -1
- pyedb/dotnet/database/utilities/__init__.py +22 -0
- pyedb/dotnet/database/utilities/heatsink.py +23 -0
- pyedb/dotnet/database/utilities/hfss_simulation_setup.py +1 -1
- pyedb/dotnet/database/utilities/obj_base.py +1 -1
- pyedb/dotnet/database/utilities/simulation_setup.py +1 -1
- pyedb/dotnet/database/utilities/siwave_cpa_simulation_setup.py +22 -0
- pyedb/dotnet/database/utilities/siwave_simulation_setup.py +22 -0
- pyedb/dotnet/database/utilities/value.py +1 -1
- pyedb/dotnet/edb.py +49 -122
- pyedb/edb_logger.py +1 -1
- pyedb/exceptions.py +22 -0
- pyedb/extensions/__init__.py +21 -0
- pyedb/extensions/create_cell_array.py +1 -1
- pyedb/extensions/via_design_backend.py +22 -0
- pyedb/generic/__init__.py +21 -0
- pyedb/generic/constants.py +1 -1
- pyedb/generic/data_handlers.py +22 -0
- pyedb/generic/design_types.py +1 -1
- pyedb/generic/filesystem.py +22 -0
- pyedb/generic/general_methods.py +1 -1
- pyedb/generic/grpc_warnings.py +22 -0
- pyedb/generic/plot.py +22 -0
- pyedb/generic/process.py +29 -2
- pyedb/generic/settings.py +1 -1
- pyedb/grpc/__init__.py +21 -0
- pyedb/grpc/database/__init__.py +21 -0
- pyedb/grpc/database/_typing.py +21 -0
- pyedb/grpc/database/components.py +9 -8
- pyedb/grpc/database/control_file.py +1 -1
- pyedb/grpc/database/definition/__init__.py +21 -0
- pyedb/grpc/database/definition/component_def.py +1 -1
- pyedb/grpc/database/definition/component_model.py +1 -1
- pyedb/grpc/database/definition/component_pin.py +1 -1
- pyedb/grpc/database/definition/materials.py +1 -1
- pyedb/grpc/database/definition/n_port_component_model.py +1 -1
- pyedb/grpc/database/definition/package_def.py +1 -1
- pyedb/grpc/database/definition/padstack_def.py +1 -1
- pyedb/grpc/database/definitions.py +1 -1
- pyedb/grpc/database/general.py +1 -1
- pyedb/grpc/database/geometry/__init__.py +21 -0
- pyedb/grpc/database/geometry/arc_data.py +1 -1
- pyedb/grpc/database/geometry/point_3d_data.py +1 -1
- pyedb/grpc/database/geometry/point_data.py +1 -1
- pyedb/grpc/database/geometry/polygon_data.py +1 -1
- pyedb/grpc/database/hfss.py +1 -1
- pyedb/grpc/database/hierarchy/__init__.py +21 -0
- pyedb/grpc/database/hierarchy/component.py +1 -1
- pyedb/grpc/database/hierarchy/model.py +1 -1
- pyedb/grpc/database/hierarchy/netlist_model.py +1 -1
- pyedb/grpc/database/hierarchy/pin_pair_model.py +1 -1
- pyedb/grpc/database/hierarchy/pingroup.py +1 -1
- pyedb/grpc/database/hierarchy/s_parameter_model.py +1 -1
- pyedb/grpc/database/hierarchy/spice_model.py +1 -1
- pyedb/grpc/database/layers/__init__.py +21 -0
- pyedb/grpc/database/layers/layer.py +22 -0
- pyedb/grpc/database/layers/stackup_layer.py +1 -1
- pyedb/grpc/database/layout/__init__.py +21 -0
- pyedb/grpc/database/layout/cell.py +1 -1
- pyedb/grpc/database/layout/layout.py +1 -1
- pyedb/grpc/database/layout/voltage_regulator.py +1 -1
- pyedb/grpc/database/layout_validation.py +1 -1
- pyedb/grpc/database/modeler.py +31 -9
- pyedb/grpc/database/net/__init__.py +21 -0
- pyedb/grpc/database/net/differential_pair.py +1 -1
- pyedb/grpc/database/net/extended_net.py +1 -1
- pyedb/grpc/database/net/net.py +1 -1
- pyedb/grpc/database/net/net_class.py +1 -1
- pyedb/grpc/database/nets.py +1 -1
- pyedb/grpc/database/padstacks.py +8 -3
- pyedb/grpc/database/ports/__init__.py +21 -0
- pyedb/grpc/database/ports/ports.py +1 -1
- pyedb/grpc/database/primitive/__init__.py +22 -0
- pyedb/grpc/database/primitive/bondwire.py +1 -1
- pyedb/grpc/database/primitive/circle.py +1 -1
- pyedb/grpc/database/primitive/padstack_instance.py +21 -16
- pyedb/grpc/database/primitive/path.py +1 -1
- pyedb/grpc/database/primitive/polygon.py +6 -4
- pyedb/grpc/database/primitive/primitive.py +1 -6
- pyedb/grpc/database/primitive/rectangle.py +1 -1
- pyedb/grpc/database/simulation_setup/__init__.py +21 -0
- pyedb/grpc/database/simulation_setup/adaptive_frequency.py +1 -1
- pyedb/grpc/database/simulation_setup/hfss_advanced_meshing_settings.py +1 -1
- pyedb/grpc/database/simulation_setup/hfss_advanced_settings.py +1 -1
- pyedb/grpc/database/simulation_setup/hfss_dcr_settings.py +1 -1
- pyedb/grpc/database/simulation_setup/hfss_general_settings.py +1 -1
- pyedb/grpc/database/simulation_setup/hfss_settings_options.py +1 -1
- pyedb/grpc/database/simulation_setup/hfss_simulation_settings.py +1 -1
- pyedb/grpc/database/simulation_setup/hfss_simulation_setup.py +1 -1
- pyedb/grpc/database/simulation_setup/hfss_solver_settings.py +1 -1
- pyedb/grpc/database/simulation_setup/mesh_operation.py +1 -1
- pyedb/grpc/database/simulation_setup/raptor_x_advanced_settings.py +1 -1
- pyedb/grpc/database/simulation_setup/raptor_x_general_settings.py +1 -1
- pyedb/grpc/database/simulation_setup/raptor_x_simulation_settings.py +1 -1
- pyedb/grpc/database/simulation_setup/raptor_x_simulation_setup.py +1 -1
- pyedb/grpc/database/simulation_setup/siwave_cpa_simulation_setup.py +22 -0
- pyedb/grpc/database/simulation_setup/siwave_dcir_simulation_setup.py +1 -1
- pyedb/grpc/database/simulation_setup/siwave_simulation_setup.py +1 -1
- pyedb/grpc/database/simulation_setup/sweep_data.py +1 -1
- pyedb/grpc/database/siwave.py +1 -1
- pyedb/grpc/database/source_excitations.py +1 -1
- pyedb/grpc/database/stackup.py +1 -1
- pyedb/grpc/database/terminal/__init__.py +21 -0
- pyedb/grpc/database/terminal/bundle_terminal.py +1 -1
- pyedb/grpc/database/terminal/edge_terminal.py +1 -1
- pyedb/grpc/database/terminal/padstack_instance_terminal.py +1 -1
- pyedb/grpc/database/terminal/pingroup_terminal.py +1 -1
- pyedb/grpc/database/terminal/point_terminal.py +1 -1
- pyedb/grpc/database/terminal/terminal.py +1 -1
- pyedb/grpc/database/utility/__init__.py +22 -0
- pyedb/grpc/database/utility/constants.py +1 -1
- pyedb/grpc/database/utility/heat_sink.py +1 -1
- pyedb/grpc/database/utility/hfss_extent_info.py +1 -1
- pyedb/grpc/database/utility/layout_statistics.py +1 -1
- pyedb/grpc/database/utility/rlc.py +1 -1
- pyedb/grpc/database/utility/sources.py +1 -1
- pyedb/grpc/database/utility/sweep_data_distribution.py +1 -1
- pyedb/grpc/database/utility/value.py +1 -1
- pyedb/grpc/database/utility/xml_control_file.py +1 -1
- pyedb/grpc/edb.py +167 -996
- pyedb/grpc/edb_init.py +8 -20
- pyedb/grpc/rpc_session.py +1 -1
- pyedb/ipc2581/__init__.py +21 -0
- pyedb/ipc2581/bom/__init__.py +21 -0
- pyedb/ipc2581/bom/bom.py +1 -1
- pyedb/ipc2581/bom/bom_item.py +1 -1
- pyedb/ipc2581/bom/characteristics.py +1 -1
- pyedb/ipc2581/bom/refdes.py +1 -1
- pyedb/ipc2581/content/__init__.py +21 -0
- pyedb/ipc2581/content/color.py +1 -1
- pyedb/ipc2581/content/content.py +1 -1
- pyedb/ipc2581/content/dictionary_color.py +1 -1
- pyedb/ipc2581/content/dictionary_fill.py +1 -1
- pyedb/ipc2581/content/dictionary_line.py +1 -1
- pyedb/ipc2581/content/entry_color.py +1 -1
- pyedb/ipc2581/content/entry_line.py +1 -1
- pyedb/ipc2581/content/fill.py +1 -1
- pyedb/ipc2581/content/layer_ref.py +1 -1
- pyedb/ipc2581/content/standard_geometries_dictionary.py +1 -1
- pyedb/ipc2581/ecad/__init__.py +21 -0
- pyedb/ipc2581/ecad/cad_data/__init__.py +21 -0
- pyedb/ipc2581/ecad/cad_data/assembly_drawing.py +1 -1
- pyedb/ipc2581/ecad/cad_data/cad_data.py +1 -1
- pyedb/ipc2581/ecad/cad_data/component.py +1 -1
- pyedb/ipc2581/ecad/cad_data/drill.py +1 -1
- pyedb/ipc2581/ecad/cad_data/feature.py +1 -1
- pyedb/ipc2581/ecad/cad_data/layer.py +1 -1
- pyedb/ipc2581/ecad/cad_data/layer_feature.py +1 -1
- pyedb/ipc2581/ecad/cad_data/logical_net.py +1 -1
- pyedb/ipc2581/ecad/cad_data/outline.py +1 -1
- pyedb/ipc2581/ecad/cad_data/package.py +1 -1
- pyedb/ipc2581/ecad/cad_data/padstack_def.py +1 -1
- pyedb/ipc2581/ecad/cad_data/padstack_hole_def.py +1 -1
- pyedb/ipc2581/ecad/cad_data/padstack_instance.py +1 -1
- pyedb/ipc2581/ecad/cad_data/padstack_pad_def.py +1 -1
- pyedb/ipc2581/ecad/cad_data/path.py +1 -1
- pyedb/ipc2581/ecad/cad_data/phy_net.py +1 -1
- pyedb/ipc2581/ecad/cad_data/pin.py +1 -1
- pyedb/ipc2581/ecad/cad_data/polygon.py +1 -1
- pyedb/ipc2581/ecad/cad_data/profile.py +1 -1
- pyedb/ipc2581/ecad/cad_data/stackup.py +1 -1
- pyedb/ipc2581/ecad/cad_data/stackup_group.py +1 -1
- pyedb/ipc2581/ecad/cad_data/stackup_layer.py +1 -1
- pyedb/ipc2581/ecad/cad_data/step.py +1 -1
- pyedb/ipc2581/ecad/cad_header.py +1 -1
- pyedb/ipc2581/ecad/ecad.py +1 -1
- pyedb/ipc2581/ecad/spec.py +1 -1
- pyedb/ipc2581/history_record.py +1 -1
- pyedb/ipc2581/ipc2581.py +1 -1
- pyedb/ipc2581/logistic_header.py +1 -1
- pyedb/libraries/common.py +1 -1
- pyedb/libraries/rf_libraries/base_functions.py +1 -1
- pyedb/libraries/rf_libraries/planar_antennas.py +1 -1
- pyedb/misc/__init__.py +21 -0
- pyedb/misc/aedtlib_personalib_install.py +1 -1
- pyedb/misc/decorators.py +22 -0
- pyedb/misc/downloads.py +1 -1
- pyedb/misc/misc.py +1 -1
- pyedb/misc/siw_feature_config/__init__.py +21 -0
- pyedb/misc/siw_feature_config/emc/__init__.py +21 -0
- pyedb/misc/siw_feature_config/emc/component_tags.py +22 -0
- pyedb/misc/siw_feature_config/emc/net_tags.py +22 -0
- pyedb/misc/siw_feature_config/emc/tag_library.py +22 -0
- pyedb/misc/siw_feature_config/emc/xml_generic.py +22 -0
- pyedb/misc/siw_feature_config/emc_rule_checker_settings.py +1 -1
- pyedb/misc/siw_feature_config/xtalk_scan/fd_xtalk_scan_config.py +1 -1
- pyedb/misc/siw_feature_config/xtalk_scan/impedance_scan_config.py +1 -1
- pyedb/misc/siw_feature_config/xtalk_scan/net.py +1 -1
- pyedb/misc/siw_feature_config/xtalk_scan/pins.py +1 -1
- pyedb/misc/siw_feature_config/xtalk_scan/scan_config.py +1 -1
- pyedb/misc/siw_feature_config/xtalk_scan/td_xtalk_config.py +1 -1
- pyedb/misc/utilities.py +1 -1
- pyedb/modeler/geometry_operators.py +22 -0
- pyedb/siwave.py +22 -0
- pyedb/siwave_core/__init__.py +21 -0
- pyedb/siwave_core/cpa/__init__.py +21 -0
- pyedb/siwave_core/cpa/simulation_setup_data_model.py +22 -0
- pyedb/siwave_core/icepak.py +1 -1
- pyedb/siwave_core/product_properties.py +23 -0
- pyedb/workflow.py +22 -0
- pyedb/workflows/sipi/hfss_auto_configuration.py +711 -0
- pyedb/workflows/utilities/__init__.py +0 -0
- pyedb/workflows/utilities/cutout.py +1428 -0
- {pyedb-0.58.0.dist-info → pyedb-0.60.0.dist-info}/METADATA +1 -1
- pyedb-0.60.0.dist-info/RECORD +308 -0
- {pyedb-0.58.0.dist-info → pyedb-0.60.0.dist-info}/licenses/LICENSE +7 -7
- pyedb-0.58.0.dist-info/RECORD +0 -305
- {pyedb-0.58.0.dist-info → pyedb-0.60.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
# Copyright (C) 2023 - 2025 ANSYS, Inc. and/or its affiliates.
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
#
|
|
4
|
+
#
|
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
# furnished to do so, subject to the following conditions:
|
|
11
|
+
#
|
|
12
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
# copies or substantial portions of the Software.
|
|
14
|
+
#
|
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
# SOFTWARE.
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from collections import defaultdict
|
|
26
|
+
from dataclasses import dataclass, field
|
|
27
|
+
import os
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
import re
|
|
30
|
+
import shutil
|
|
31
|
+
import stat
|
|
32
|
+
from typing import Dict, List, Optional, Sequence, Tuple, Union
|
|
33
|
+
|
|
34
|
+
from pyedb import Edb
|
|
35
|
+
|
|
36
|
+
# patterns used for Regex matching of ground/reference nets
|
|
37
|
+
ref_patterns = [
|
|
38
|
+
r"^GND\d*$",
|
|
39
|
+
r"^GND_\w+",
|
|
40
|
+
r"^GND$",
|
|
41
|
+
r"^VSS\d*$",
|
|
42
|
+
r"^VSS\w*",
|
|
43
|
+
r"^DGND$",
|
|
44
|
+
r"^AGND$",
|
|
45
|
+
r"^PGND$",
|
|
46
|
+
r"^EGND$",
|
|
47
|
+
r"^SGND$",
|
|
48
|
+
r"^REF$",
|
|
49
|
+
r"^VREF[A-Z0-9]*",
|
|
50
|
+
r"^VREF$",
|
|
51
|
+
r"^VREF_\d+\.\d+V$",
|
|
52
|
+
r".*_REF$",
|
|
53
|
+
r".*REF$",
|
|
54
|
+
r"^VR[A-Z0-9]*",
|
|
55
|
+
r"^VTT$",
|
|
56
|
+
r"^VTT\d*V$",
|
|
57
|
+
r"^VDDQ_REF$",
|
|
58
|
+
r"^VPP$",
|
|
59
|
+
r"^VCCO_\w+",
|
|
60
|
+
r"^VCCA_\w+",
|
|
61
|
+
r"^VCCD_\w+",
|
|
62
|
+
r"^VSYS$",
|
|
63
|
+
r"^VBUS$",
|
|
64
|
+
r"^0V$",
|
|
65
|
+
r"^0V_\w+",
|
|
66
|
+
r"^GND plane$",
|
|
67
|
+
r"^GROUND$",
|
|
68
|
+
r"^SENSE\d*$",
|
|
69
|
+
r"^KSENSE\w*",
|
|
70
|
+
r"^CAL\d*$",
|
|
71
|
+
r"^CAL_\w+",
|
|
72
|
+
r"^VCM\d*$",
|
|
73
|
+
r"^VCM\w+",
|
|
74
|
+
r"^BGREF$",
|
|
75
|
+
r"^BGVREF$",
|
|
76
|
+
r"^VREFP$",
|
|
77
|
+
r"^VREFN$",
|
|
78
|
+
r"^AVSS$",
|
|
79
|
+
r"^AVDD$",
|
|
80
|
+
r"^DVSS$",
|
|
81
|
+
r"^DVDD$",
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
combined_ref = re.compile("|".join("(?:%s)" % p for p in ref_patterns), re.I)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass
|
|
88
|
+
class SolderBallsInfo:
|
|
89
|
+
ref_des: str = field(default="")
|
|
90
|
+
shape: str = field(default="cylinder")
|
|
91
|
+
diameter: Optional[Union[str, float]] = None
|
|
92
|
+
mid_diameter: Optional[Union[str, float]] = None
|
|
93
|
+
height: Optional[Union[str, float]] = None
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
@dataclass
|
|
97
|
+
class SimulationSetup:
|
|
98
|
+
meshing_frequency: Union[str, float] = field(default="10GHz")
|
|
99
|
+
maximum_pass_number: int = field(default=15)
|
|
100
|
+
start_frequency: Union[str, float] = field(default=0)
|
|
101
|
+
stop_frequency: Union[str, float] = field(default="40GHz")
|
|
102
|
+
frequency_step: Union[str, float] = field(default="0.05GHz")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@dataclass
|
|
106
|
+
class BatchGroup:
|
|
107
|
+
name: str = field(default="")
|
|
108
|
+
nets: List[str] = field(default_factory=list)
|
|
109
|
+
simulation_setup: SimulationSetup = None # if None, use default in auto config
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class HFSSAutoConfiguration:
|
|
113
|
+
def __init__(self, edb=None):
|
|
114
|
+
self._pedb = edb
|
|
115
|
+
self.ansys_version: str = "2025.2"
|
|
116
|
+
self.grpc: bool = True
|
|
117
|
+
self.source_edb_path: str = ""
|
|
118
|
+
self.target_edb_path: str = ""
|
|
119
|
+
self.batch_group_folder: str = ""
|
|
120
|
+
self.signal_nets: list = []
|
|
121
|
+
self.power_nets: list = []
|
|
122
|
+
self.reference_net: str = ""
|
|
123
|
+
self.batch_size: int = 100
|
|
124
|
+
self.batch_groups: list[BatchGroup] = []
|
|
125
|
+
self.components: list[str] = []
|
|
126
|
+
self.solder_balls: list[SolderBallsInfo] = []
|
|
127
|
+
self.simulation_setup: SimulationSetup = SimulationSetup()
|
|
128
|
+
self.extent_type: str = "bounding_box"
|
|
129
|
+
self.cutout_expansion: Union[float, str] = "2mm"
|
|
130
|
+
self.auto_mesh_seeding: bool = True
|
|
131
|
+
self.port_type: str = "coaxial"
|
|
132
|
+
self.create_pin_group: bool = False
|
|
133
|
+
|
|
134
|
+
_DIFF_SUFFIX = re.compile(r"_[PN]$|_[ML]$|_[+-]$", re.I)
|
|
135
|
+
|
|
136
|
+
def auto_populate_batch_groups(
|
|
137
|
+
self,
|
|
138
|
+
pattern: str | list[str] | None = None,
|
|
139
|
+
) -> None:
|
|
140
|
+
"""
|
|
141
|
+
Automatically create and populate :attr:`batch_groups` from the current
|
|
142
|
+
:attr:`signal_nets`.
|
|
143
|
+
|
|
144
|
+
This is a thin convenience wrapper around :meth:`group_nets_by_prefix`.
|
|
145
|
+
It **only** executes when both:
|
|
146
|
+
|
|
147
|
+
* :attr:`auto_evaluate_batch_groups` is ``True``, and
|
|
148
|
+
* :attr:`signal_nets` is non-empty.
|
|
149
|
+
|
|
150
|
+
Parameters
|
|
151
|
+
----------
|
|
152
|
+
pattern : :class:`str` | :class:`list` [:class:`str`] | ``None``, optional
|
|
153
|
+
POSIX ERE prefix pattern(s) that control which nets are grouped.
|
|
154
|
+
|
|
155
|
+
* ``None`` *(default)* – activate **auto-discovery** mode: nets are
|
|
156
|
+
clustered heuristically and then split into chunks of size
|
|
157
|
+
:attr:`batch_size`.
|
|
158
|
+
* :class:`str` – treat the single string as a prefix pattern
|
|
159
|
+
(automatically anchored: ``pattern + ".*"``).
|
|
160
|
+
* :class:`list` [:class:`str`] – each list element becomes its own
|
|
161
|
+
prefix pattern; one :class:`.BatchGroup` is created **per list
|
|
162
|
+
entry**, regardless of :attr:`batch_size`.
|
|
163
|
+
|
|
164
|
+
Side-effects
|
|
165
|
+
------------
|
|
166
|
+
Clears and repopulates :attr:`batch_groups` in-place.
|
|
167
|
+
"""
|
|
168
|
+
if not self._pedb:
|
|
169
|
+
self._pedb = Edb(edbpath=self.source_edb_path, version=self.ansys_version, grpc=self.grpc)
|
|
170
|
+
self.signal_nets = list(self._pedb.nets.signal.keys())
|
|
171
|
+
all_power_nets = list(self._pedb.nets.power.keys())
|
|
172
|
+
reference_nets = [n for n in all_power_nets if combined_ref.match(n)]
|
|
173
|
+
|
|
174
|
+
# --- guarantee: any net whose *upper-case* name contains "GND" comes first ---
|
|
175
|
+
def __key(n):
|
|
176
|
+
return (0, n) if "GND" in n.upper() else (1, n)
|
|
177
|
+
|
|
178
|
+
_ref_nets = list(sorted(reference_nets, key=__key))
|
|
179
|
+
if len(_ref_nets) > 1:
|
|
180
|
+
self._pedb.logger.warning(
|
|
181
|
+
f"Multiple candidate reference nets found: {_ref_nets}. Using {_ref_nets[0]} as the reference net."
|
|
182
|
+
)
|
|
183
|
+
self.reference_net = _ref_nets[0]
|
|
184
|
+
self.power_nets = [n for n in all_power_nets if n not in _ref_nets]
|
|
185
|
+
self.group_nets_by_prefix(pattern)
|
|
186
|
+
self._pedb.close(terminate_rpc_session=False)
|
|
187
|
+
|
|
188
|
+
def add_batch_group(
|
|
189
|
+
self,
|
|
190
|
+
name: str,
|
|
191
|
+
nets: Sequence[str] | None = None,
|
|
192
|
+
*,
|
|
193
|
+
simulation_setup: SimulationSetup | None = None,
|
|
194
|
+
) -> BatchGroup:
|
|
195
|
+
"""
|
|
196
|
+
Append a new BatchGroup to the configuration.
|
|
197
|
+
|
|
198
|
+
Parameters
|
|
199
|
+
----------
|
|
200
|
+
name : str
|
|
201
|
+
Descriptive name for the group (will also become the regex
|
|
202
|
+
pattern when the group is built automatically).
|
|
203
|
+
nets : Sequence[str], optional
|
|
204
|
+
List of net names that belong to this batch. If omitted
|
|
205
|
+
an empty list is assumed and you can fill it later.
|
|
206
|
+
simulation_setup : SimulationSetup, optional
|
|
207
|
+
Per-batch simulation settings. When None the global
|
|
208
|
+
``self.simulation_setup`` is used.
|
|
209
|
+
|
|
210
|
+
Returns
|
|
211
|
+
-------
|
|
212
|
+
BatchGroup
|
|
213
|
+
The freshly created instance (already appended to
|
|
214
|
+
``self.batch_groups``) so the caller can further
|
|
215
|
+
manipulate it if desired.
|
|
216
|
+
"""
|
|
217
|
+
bg = BatchGroup(
|
|
218
|
+
name=name,
|
|
219
|
+
nets=list(nets or []),
|
|
220
|
+
simulation_setup=simulation_setup,
|
|
221
|
+
)
|
|
222
|
+
self.batch_groups.append(bg)
|
|
223
|
+
return bg
|
|
224
|
+
|
|
225
|
+
def add_solder_ball(
|
|
226
|
+
self,
|
|
227
|
+
ref_des: str,
|
|
228
|
+
shape: str = "cylinder",
|
|
229
|
+
diameter: Optional[Union[str, float]] = None,
|
|
230
|
+
mid_diameter: Optional[Union[str, float]] = None,
|
|
231
|
+
height: Optional[Union[str, float]] = None,
|
|
232
|
+
) -> SolderBallsInfo:
|
|
233
|
+
"""
|
|
234
|
+
Append a new :class:`.SolderBallsInfo` entry to the configuration.
|
|
235
|
+
|
|
236
|
+
Parameters
|
|
237
|
+
----------
|
|
238
|
+
ref_des : :class:`str`
|
|
239
|
+
Reference designator of the component to which the solder-ball
|
|
240
|
+
definition applies (e.g. ``"U1"``).
|
|
241
|
+
shape : :class:`str`, default ``"cylinder"``
|
|
242
|
+
Geometric model used for the solder ball. Supported values are
|
|
243
|
+
``"cylinder"``, ``"sphere"``, ``"spheroid"``, etc.
|
|
244
|
+
diameter : :class:`str` | :class:`float` | ``None``, optional
|
|
245
|
+
Nominal diameter. When ``None`` HFSS auto-evaluates the value
|
|
246
|
+
from the footprint.
|
|
247
|
+
mid_diameter : :class:`str` | :class:`float` | ``None``, optional
|
|
248
|
+
Middle diameter **required only for spheroid shapes**. Ignored
|
|
249
|
+
for all other geometries.
|
|
250
|
+
height : :class:`str` | :class:`float` | ``None``, optional
|
|
251
|
+
Ball height. When ``None`` HFSS computes an appropriate value
|
|
252
|
+
automatically.
|
|
253
|
+
|
|
254
|
+
Returns
|
|
255
|
+
-------
|
|
256
|
+
:class:`.SolderBallsInfo`
|
|
257
|
+
The newly created instance (already appended to
|
|
258
|
+
:attr:`solder_balls`). The object can be further edited in-place
|
|
259
|
+
by the caller if desired.
|
|
260
|
+
|
|
261
|
+
Examples
|
|
262
|
+
--------
|
|
263
|
+
>>> cfg = HfssAutoConfig()
|
|
264
|
+
>>> cfg.add_solder_ball("U1", diameter="0.3mm", height="0.2mm")
|
|
265
|
+
>>> cfg.add_solder_ball(
|
|
266
|
+
... "U2",
|
|
267
|
+
... shape="spheroid",
|
|
268
|
+
... diameter="0.25mm",
|
|
269
|
+
... mid_diameter="0.35mm",
|
|
270
|
+
... height="0.18mm",
|
|
271
|
+
... )
|
|
272
|
+
"""
|
|
273
|
+
sb = SolderBallsInfo(
|
|
274
|
+
ref_des=ref_des,
|
|
275
|
+
shape=shape,
|
|
276
|
+
diameter=diameter,
|
|
277
|
+
mid_diameter=mid_diameter,
|
|
278
|
+
height=height,
|
|
279
|
+
)
|
|
280
|
+
self.solder_balls.append(sb)
|
|
281
|
+
return sb
|
|
282
|
+
|
|
283
|
+
def add_simulation_setup(
|
|
284
|
+
self,
|
|
285
|
+
meshing_frequency: Optional[Union[str, float]] = "10GHz",
|
|
286
|
+
maximum_pass_number: int = 15,
|
|
287
|
+
start_frequency: Optional[Union[str, float]] = 0,
|
|
288
|
+
stop_frequency: Optional[Union[str, float]] = "40GHz",
|
|
289
|
+
frequency_step: Optional[Union[str, float]] = "0.05GHz",
|
|
290
|
+
replace: bool = True,
|
|
291
|
+
) -> SimulationSetup:
|
|
292
|
+
r"""
|
|
293
|
+
Create a: class:`.SimulationSetup` instance and attach it to the configuration.
|
|
294
|
+
|
|
295
|
+
Parameters
|
|
296
|
+
----------
|
|
297
|
+
meshing_frequency : Union[:class:`str`,: class:`float`], default ``"10GHz"``
|
|
298
|
+
Driven frequency used during mesh generation.
|
|
299
|
+
maximum_pass_number : class:`int`, default ``15``
|
|
300
|
+
Maximum number of adaptive passes.
|
|
301
|
+
start_frequency : Union[:class:`str`,: class:`float`], default ``0``
|
|
302
|
+
Lower bound of the sweep window.
|
|
303
|
+
stop_frequency : Union[:class:`str`,: class:`float`], default ``"40GHz"``
|
|
304
|
+
Upper bound of the sweep window.
|
|
305
|
+
frequency_step : Union[:class:`str`,: class:`float`], default ``"0.05GHz"``
|
|
306
|
+
Linear step size for the frequency sweep.
|
|
307
|
+
mesh_operation_size : Union[:class:`str`,: class:`float`, ``None``], optional
|
|
308
|
+
Maximum element size for mesh operations. When ``None`` HFSS
|
|
309
|
+
computes an appropriate value automatically.
|
|
310
|
+
replace : class:`bool`, default ``False``
|
|
311
|
+
Placement strategy for the new setup:
|
|
312
|
+
|
|
313
|
+
* ``False`` – append a *per-batch* setup by creating an auxiliary
|
|
314
|
+
:class:`.BatchGroup` (``name="extra_setup"``) whose
|
|
315
|
+
:attr:`.BatchGroup.simulation_setup` points to the new object.
|
|
316
|
+
* ``True`` – overwrite the **global**: attr:`simulation_setup`
|
|
317
|
+
attribute of the current :class:`.HfssAutoConfig` instance.
|
|
318
|
+
|
|
319
|
+
Returns
|
|
320
|
+
-------
|
|
321
|
+
:class:`.SimulationSetup`
|
|
322
|
+
The newly created instance (already stored inside the configuration).
|
|
323
|
+
|
|
324
|
+
Examples
|
|
325
|
+
--------
|
|
326
|
+
>>> cfg = HfssAutoConfig()
|
|
327
|
+
>>> # global setup
|
|
328
|
+
>>> cfg.add_simulation_setup(frequency_max="60GHz", replace=True)
|
|
329
|
+
>>> # per-batch setup
|
|
330
|
+
>>> cfg.add_simulation_setup(frequency_step="0.1GHz")
|
|
331
|
+
"""
|
|
332
|
+
setup = SimulationSetup(
|
|
333
|
+
meshing_frequency=meshing_frequency,
|
|
334
|
+
maximum_pass_number=maximum_pass_number,
|
|
335
|
+
start_frequency=start_frequency,
|
|
336
|
+
stop_frequency=stop_frequency,
|
|
337
|
+
frequency_step=frequency_step,
|
|
338
|
+
)
|
|
339
|
+
if replace:
|
|
340
|
+
self.simulation_setup = setup
|
|
341
|
+
else:
|
|
342
|
+
self.batch_groups.append(BatchGroup(name="extra_setup", simulation_setup=setup))
|
|
343
|
+
return setup
|
|
344
|
+
|
|
345
|
+
@staticmethod
|
|
346
|
+
def _longest_common_prefix(strings: Sequence[str]) -> str:
|
|
347
|
+
if not strings:
|
|
348
|
+
return ""
|
|
349
|
+
normed = [re.sub(r"[^A-Za-z0-9_]", "", s).upper() for s in strings]
|
|
350
|
+
s_min, s_max = min(normed), max(normed)
|
|
351
|
+
idx = 0
|
|
352
|
+
while idx < len(s_min) and s_min[idx] == s_max[idx]:
|
|
353
|
+
idx += 1
|
|
354
|
+
return strings[0][:idx]
|
|
355
|
+
|
|
356
|
+
def _infer_prefix_patterns(self, nets: Sequence[str]) -> List[str]:
|
|
357
|
+
if not nets:
|
|
358
|
+
return []
|
|
359
|
+
total = len(nets)
|
|
360
|
+
min_group_size = max(1, int(0.05 * total))
|
|
361
|
+
groups: List[Tuple[str, List[str]]] = []
|
|
362
|
+
for net in sorted(nets):
|
|
363
|
+
if groups:
|
|
364
|
+
last_prefix, last_members = groups[-1]
|
|
365
|
+
trial_prefix = self._longest_common_prefix(last_members + [net])
|
|
366
|
+
if trial_prefix and len(last_members) + 1 >= min_group_size:
|
|
367
|
+
groups[-1] = (trial_prefix, last_members + [net])
|
|
368
|
+
continue
|
|
369
|
+
groups.append((net, [net]))
|
|
370
|
+
return [re.escape(pfx) + r".*" for pfx, _ in groups]
|
|
371
|
+
|
|
372
|
+
def _base_name(self, net: str) -> str:
|
|
373
|
+
return self._DIFF_SUFFIX.sub("", net)
|
|
374
|
+
|
|
375
|
+
def _build_diff_pairs(self, nets: Sequence[str]) -> List[Tuple[str, List[str]]]:
|
|
376
|
+
buckets: Dict[str, List[str]] = defaultdict(list)
|
|
377
|
+
for n in nets:
|
|
378
|
+
buckets[self._base_name(n)].append(n)
|
|
379
|
+
clusters = []
|
|
380
|
+
for base, members in buckets.items():
|
|
381
|
+
if len(members) >= 2 or not self._DIFF_SUFFIX.search(members[0]):
|
|
382
|
+
clusters.append((base, sorted(members)))
|
|
383
|
+
return clusters
|
|
384
|
+
|
|
385
|
+
def group_nets_by_prefix(
|
|
386
|
+
self,
|
|
387
|
+
prefix_patterns: Optional[Sequence[str]] = None,
|
|
388
|
+
) -> Dict[str, List[List[str]]]:
|
|
389
|
+
r"""
|
|
390
|
+
Group signal nets into *disjoint* batches while preserving differential pairs.
|
|
391
|
+
|
|
392
|
+
Behaviour in a nutshell
|
|
393
|
+
-----------------------
|
|
394
|
+
1. Nets that form differential pairs (``PCIe_RX0_P`` / ``PCIe_RX0_N``, ``USB3_TX_M`` / ``USB3_TX_P`` …)
|
|
395
|
+
are **never split**; they always appear in the **same** batch.
|
|
396
|
+
2. Every net is assigned to **exactly one** batch.
|
|
397
|
+
3. No batch contains only a single net; orphans are merged into the largest compatible group.
|
|
398
|
+
4. When *prefix_patterns* is supplied **only** nets that match one of those patterns are
|
|
399
|
+
returned; everything else is silently ignored.
|
|
400
|
+
5. If *prefix_patterns* is supplied the caller gets **one group per pattern** regardless of
|
|
401
|
+
:attr:`batch_size`; when it is ``None`` the legacy auto-discovery mode is used and
|
|
402
|
+
:attr:`batch_size` is honoured.
|
|
403
|
+
|
|
404
|
+
Parameters
|
|
405
|
+
----------
|
|
406
|
+
prefix_patterns : Sequence[str], optional
|
|
407
|
+
POSIX ERE patterns that define the prefixes to be grouped.
|
|
408
|
+
Example: ``["PCIe", "USB"]`` ➜ interpreted as ``["PCIe.*", "USB.*"]``.
|
|
409
|
+
If ``None`` patterns are derived heuristically from the data set
|
|
410
|
+
(see :meth:`_infer_prefix_patterns`).
|
|
411
|
+
|
|
412
|
+
Returns
|
|
413
|
+
-------
|
|
414
|
+
Dict[str, List[List[str]]]
|
|
415
|
+
Keys are the original / generated pattern strings.
|
|
416
|
+
Values are lists of batches; each batch is an alphabetically sorted
|
|
417
|
+
list of net names. When *prefix_patterns* was supplied the list
|
|
418
|
+
contains **exactly one** element (the complete group); in auto-discovery
|
|
419
|
+
mode the list may contain multiple slices sized according to
|
|
420
|
+
:attr:`batch_size`.
|
|
421
|
+
|
|
422
|
+
Examples
|
|
423
|
+
--------
|
|
424
|
+
Explicit grouping (production intent)::
|
|
425
|
+
|
|
426
|
+
>>> cfg.signal_nets = ["PCIe_RX0_P", "PCIe_RX0_N", "PCIe_TX0_P",
|
|
427
|
+
... "USB3_DP", "USB3_DN", "DDR4_A0", "DDR4_A1"]
|
|
428
|
+
>>> cfg.batch_size = 1_000 # ignored when patterns are supplied
|
|
429
|
+
>>> cfg.group_nets_by_prefix(["PCIe", "USB"])
|
|
430
|
+
{'PCIe.*': [['PCIe_RX0_N', 'PCIe_RX0_P', 'PCIe_TX0_P']],
|
|
431
|
+
'USB.*': [['USB3_DN', 'USB3_DP']]}
|
|
432
|
+
|
|
433
|
+
Auto-discovery with batching::
|
|
434
|
+
|
|
435
|
+
>>> cfg.group_nets_by_prefix() # batch_size = 2
|
|
436
|
+
{'PCIe.*': [['PCIe_RX0_N', 'PCIe_RX0_P'], ['PCIe_TX0_P']],
|
|
437
|
+
'USB.*': [['USB3_DN', 'USB3_DP']],
|
|
438
|
+
'DDR4.*': [['DDR4_A0', 'DDR4_A1']]}
|
|
439
|
+
|
|
440
|
+
Notes
|
|
441
|
+
-----
|
|
442
|
+
* Differential recognition strips the suffixes ``_[PN]``, ``_[ML]``, ``_[+-]``
|
|
443
|
+
(case-insensitive).
|
|
444
|
+
* The function updates the instance attribute :attr:`batch_groups` in place.
|
|
445
|
+
"""
|
|
446
|
+
if not self.signal_nets:
|
|
447
|
+
return {}
|
|
448
|
+
|
|
449
|
+
clusters = self._build_diff_pairs(self.signal_nets)
|
|
450
|
+
|
|
451
|
+
# ---------- 1. patterns ------------------------------------------
|
|
452
|
+
if prefix_patterns is None:
|
|
453
|
+
patterns = self._infer_prefix_patterns([base for base, _ in clusters])
|
|
454
|
+
else:
|
|
455
|
+
patterns = [p if p.endswith(".*") else p + ".*" for p in prefix_patterns]
|
|
456
|
+
|
|
457
|
+
compiled = [re.compile(p, re.I) for p in patterns]
|
|
458
|
+
|
|
459
|
+
# ---------- 2. bucket clusters ------------------------------------
|
|
460
|
+
buckets: Dict[str, List[Tuple[str, List[str]]]] = defaultdict(list)
|
|
461
|
+
for base, members in clusters:
|
|
462
|
+
for pat, orig in zip(compiled, patterns):
|
|
463
|
+
if pat.match(base):
|
|
464
|
+
buckets[orig].append((base, members))
|
|
465
|
+
break
|
|
466
|
+
|
|
467
|
+
# ---------- 3. flatten --------------------------------------------
|
|
468
|
+
flat: Dict[str, List[str]] = {}
|
|
469
|
+
for pat in patterns:
|
|
470
|
+
if pat not in buckets:
|
|
471
|
+
continue
|
|
472
|
+
flat[pat] = []
|
|
473
|
+
for _, members in buckets[pat]:
|
|
474
|
+
flat[pat].extend(members)
|
|
475
|
+
flat[pat].sort()
|
|
476
|
+
|
|
477
|
+
# ---------- 4. merge singles --------------------------------------
|
|
478
|
+
singles = [k for k, lst in flat.items() if len(lst) == 1]
|
|
479
|
+
if singles:
|
|
480
|
+
biggest = max(flat.keys(), key=lambda k: len(flat[k]))
|
|
481
|
+
for k in singles:
|
|
482
|
+
flat[biggest].extend(flat[k])
|
|
483
|
+
del flat[k]
|
|
484
|
+
flat[biggest].sort()
|
|
485
|
+
|
|
486
|
+
# ---------- 5. ONE group per supplied prefix -----------------------
|
|
487
|
+
grouped: Dict[str, List[List[str]]] = {}
|
|
488
|
+
for pat, lst in flat.items():
|
|
489
|
+
if prefix_patterns is None:
|
|
490
|
+
# old auto-mode – respect batch_size
|
|
491
|
+
if self.batch_size is None:
|
|
492
|
+
grouped[pat] = [lst]
|
|
493
|
+
else:
|
|
494
|
+
grouped[pat] = [lst[i : i + self.batch_size] for i in range(0, len(lst), self.batch_size)]
|
|
495
|
+
else:
|
|
496
|
+
# user-mode – exactly one group per requested prefix
|
|
497
|
+
grouped[pat] = [lst]
|
|
498
|
+
|
|
499
|
+
# ---------- 6. update instance -------------------------------------
|
|
500
|
+
self.batch_groups.clear()
|
|
501
|
+
for pat, batches in grouped.items():
|
|
502
|
+
for nets in batches:
|
|
503
|
+
self.batch_groups.append(BatchGroup(name=pat, nets=nets))
|
|
504
|
+
grouped = {k[:-2] if k.endswith("*") else k: v for k, v in grouped.items()}
|
|
505
|
+
for batch_group in self.batch_groups:
|
|
506
|
+
batch_group.name = batch_group.name[:-2] if batch_group.name.endswith(".*") else batch_group.name
|
|
507
|
+
return grouped
|
|
508
|
+
|
|
509
|
+
def create_projects(self):
|
|
510
|
+
def del_ro(func, path, _):
|
|
511
|
+
os.chmod(path, stat.S_IWRITE)
|
|
512
|
+
func(path)
|
|
513
|
+
|
|
514
|
+
if not self.batch_groups:
|
|
515
|
+
self._copy_edb_and_open_project()
|
|
516
|
+
if not self._pedb:
|
|
517
|
+
self._create_project(close_rpc=True)
|
|
518
|
+
else:
|
|
519
|
+
self._create_project(close_rpc=False)
|
|
520
|
+
else:
|
|
521
|
+
batch_count = 0
|
|
522
|
+
if os.path.isdir(self.batch_group_folder):
|
|
523
|
+
shutil.rmtree(self.batch_group_folder)
|
|
524
|
+
for batch_group in self.batch_groups:
|
|
525
|
+
batch_count += 1
|
|
526
|
+
if not self.batch_group_folder:
|
|
527
|
+
self.batch_group_folder = os.path.join(str(Path(self.source_edb_path).parent), "batch_groups")
|
|
528
|
+
if batch_count == 1 and os.path.isdir(self.batch_group_folder):
|
|
529
|
+
os.chdir(os.path.expanduser("~"))
|
|
530
|
+
shutil.rmtree(self.batch_group_folder, onerror=del_ro)
|
|
531
|
+
if batch_group.simulation_setup:
|
|
532
|
+
self.simulation_setup = batch_group.simulation_setup
|
|
533
|
+
self.signal_nets = batch_group.nets
|
|
534
|
+
self.target_edb_path = os.path.join(self.batch_group_folder, batch_group.name + ".aedb")
|
|
535
|
+
self._copy_edb_and_open_project()
|
|
536
|
+
if batch_count == len(self.batch_groups):
|
|
537
|
+
self._create_project(close_rpc=True)
|
|
538
|
+
else:
|
|
539
|
+
self._create_project(close_rpc=False)
|
|
540
|
+
|
|
541
|
+
def _copy_edb_and_open_project(self):
|
|
542
|
+
if not self.source_edb_path:
|
|
543
|
+
raise ValueError("source EDB path is empty.")
|
|
544
|
+
shutil.copytree(self.source_edb_path, self.target_edb_path)
|
|
545
|
+
if not os.path.isdir(self.target_edb_path):
|
|
546
|
+
raise FileNotFoundError(f"Failed to copy EDB to {self.target_edb_path}")
|
|
547
|
+
self._pedb = Edb(edbpath=self.target_edb_path, version=self.ansys_version, grpc=self.grpc)
|
|
548
|
+
|
|
549
|
+
def __get_components_using_signal_nets(self):
|
|
550
|
+
self.components = list(
|
|
551
|
+
set(
|
|
552
|
+
[
|
|
553
|
+
refdes
|
|
554
|
+
for refdes, comp in self._pedb.components.instances.items()
|
|
555
|
+
if comp.type.lower() not in ["resistor", "capacitor", "inductor"]
|
|
556
|
+
and not set(comp.nets).isdisjoint(self.signal_nets)
|
|
557
|
+
]
|
|
558
|
+
)
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
def _create_project(self, close_rpc: bool = True):
|
|
562
|
+
if not self.target_edb_path:
|
|
563
|
+
raise ValueError("Project path is empty.")
|
|
564
|
+
if not self.signal_nets:
|
|
565
|
+
raise ValueError("No signal nets defined.")
|
|
566
|
+
if not self.reference_net:
|
|
567
|
+
raise ValueError("No reference net defined.")
|
|
568
|
+
# step 1: cutout
|
|
569
|
+
self._pedb.logger.info(f"Creating project {self.target_edb_path}")
|
|
570
|
+
self._pedb.logger.info(f"step 1: cutout")
|
|
571
|
+
clipped_nets = self.power_nets
|
|
572
|
+
clipped_nets.append(self.reference_net)
|
|
573
|
+
self._pedb.cutout(
|
|
574
|
+
signal_list=self.signal_nets,
|
|
575
|
+
reference_list=clipped_nets,
|
|
576
|
+
extent_type=self.extent_type,
|
|
577
|
+
expansion_size=self.cutout_expansion,
|
|
578
|
+
)
|
|
579
|
+
# step 2: create Ports
|
|
580
|
+
self._pedb.logger.info(f"step 2: creating ports")
|
|
581
|
+
if not self.components:
|
|
582
|
+
self._pedb.logger.info("No components provided, searching component instances")
|
|
583
|
+
self.__get_components_using_signal_nets()
|
|
584
|
+
if not self.components:
|
|
585
|
+
raise ValueError("No components found in the design.")
|
|
586
|
+
if self.port_type in ["coaxial", "coax", "coax_port", "coaxial_port"]:
|
|
587
|
+
if self.solder_balls:
|
|
588
|
+
for solder_ball in self.solder_balls:
|
|
589
|
+
comp = solder_ball.ref_des
|
|
590
|
+
if not comp in self.components:
|
|
591
|
+
self._pedb.logger.warning(f"Component {comp} not found in the design, skipping")
|
|
592
|
+
continue
|
|
593
|
+
self._pedb.components.create_port_on_component(
|
|
594
|
+
component=comp,
|
|
595
|
+
net_list=self.signal_nets,
|
|
596
|
+
port_type="coax_port",
|
|
597
|
+
reference_net=self.reference_net,
|
|
598
|
+
solder_balls_height=solder_ball.height,
|
|
599
|
+
solder_balls_size=solder_ball.diameter,
|
|
600
|
+
solder_balls_mid_size=solder_ball.mid_diameter,
|
|
601
|
+
)
|
|
602
|
+
else:
|
|
603
|
+
for component in self.components:
|
|
604
|
+
self._pedb.components.create_port_on_component(
|
|
605
|
+
component=component,
|
|
606
|
+
net_list=self.signal_nets,
|
|
607
|
+
port_type="coax_port",
|
|
608
|
+
reference_net=self.reference_net,
|
|
609
|
+
)
|
|
610
|
+
elif self.port_type in ["circuit_port", "circuit", "circuit_ports"]:
|
|
611
|
+
for component in self.components:
|
|
612
|
+
self._pedb.components.create_port_on_component(
|
|
613
|
+
component=component,
|
|
614
|
+
net_list=self.signal_nets,
|
|
615
|
+
port_type="circuit_port",
|
|
616
|
+
do_pingroup=self.create_pin_group,
|
|
617
|
+
reference_net=self.reference_net,
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
self._pedb.logger.info(f"Ports created: {len(self._pedb.hfss.excitations)}")
|
|
621
|
+
# step 3: create simulation setup
|
|
622
|
+
self._pedb.logger.info(f"step 3: creating simulation setup")
|
|
623
|
+
setup = self._pedb.hfss.add_setup("Setup1")
|
|
624
|
+
setup.adaptive_settings.max_passes = self.simulation_setup.maximum_pass_number
|
|
625
|
+
if not self.grpc:
|
|
626
|
+
setup.adaptive_settings.adaptive_frequency_data_list[
|
|
627
|
+
0
|
|
628
|
+
].adaptive_frequency = self.simulation_setup.meshing_frequency
|
|
629
|
+
else:
|
|
630
|
+
setup.settings.mesh_frequency = self.simulation_setup.meshing_frequency
|
|
631
|
+
setup.add_sweep(
|
|
632
|
+
"AutoSweep",
|
|
633
|
+
start_freq=self.simulation_setup.start_frequency,
|
|
634
|
+
stop_freq=self.simulation_setup.stop_frequency,
|
|
635
|
+
step=self.simulation_setup.frequency_step,
|
|
636
|
+
)
|
|
637
|
+
if self.auto_mesh_seeding:
|
|
638
|
+
setup.auto_mesh_operation()
|
|
639
|
+
self._pedb.save()
|
|
640
|
+
self._pedb.close(terminate_rpc_session=close_rpc)
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
def create_hfss_auto_configuration(
|
|
644
|
+
edb: Optional[Edb] = None,
|
|
645
|
+
ansys_version: Optional[str] = None,
|
|
646
|
+
grpc: Optional[bool] = None,
|
|
647
|
+
source_edb_path: Optional[str] = None,
|
|
648
|
+
target_edb_path: Optional[str] = None,
|
|
649
|
+
signal_nets: Optional[list] = None,
|
|
650
|
+
power_nets: Optional[list] = None,
|
|
651
|
+
reference_net: Optional[str] = None,
|
|
652
|
+
batch_size: Optional[int] = None,
|
|
653
|
+
batch_groups: Optional[list] = None,
|
|
654
|
+
components: Optional[list[str]] = None,
|
|
655
|
+
solder_balls: Optional[list] = None,
|
|
656
|
+
simulation_setup: Optional[SimulationSetup] = None,
|
|
657
|
+
extent_type: Optional[str] = None,
|
|
658
|
+
cutout_expansion: Optional[Union[str, float]] = None,
|
|
659
|
+
auto_mesh_seeding: Optional[bool] = None,
|
|
660
|
+
port_type: Optional[str] = None,
|
|
661
|
+
create_pin_group: Optional[bool] = None,
|
|
662
|
+
) -> HFSSAutoConfiguration:
|
|
663
|
+
"""
|
|
664
|
+
Factory function that creates an HFSSAutoConfiguration instance
|
|
665
|
+
with optional overrides for every public attribute.
|
|
666
|
+
|
|
667
|
+
Parameters
|
|
668
|
+
----------
|
|
669
|
+
All parameters are optional. When omitted, the class-level defaults
|
|
670
|
+
(defined in HFSSAutoConfiguration.__init__) are kept.
|
|
671
|
+
|
|
672
|
+
Returns
|
|
673
|
+
-------
|
|
674
|
+
HFSSAutoConfiguration
|
|
675
|
+
A fully configured instance ready for further use or inspection.
|
|
676
|
+
"""
|
|
677
|
+
cfg = HFSSAutoConfiguration(edb)
|
|
678
|
+
|
|
679
|
+
# Scalar overrides
|
|
680
|
+
for attr, value in (
|
|
681
|
+
("ansys_version", ansys_version),
|
|
682
|
+
("grpc", grpc),
|
|
683
|
+
("source_edb_path", source_edb_path),
|
|
684
|
+
("target_edb_path", target_edb_path),
|
|
685
|
+
("batch_size", batch_size),
|
|
686
|
+
("extent_type", extent_type),
|
|
687
|
+
("cutout_expansion", cutout_expansion),
|
|
688
|
+
("auto_mesh_seeding", auto_mesh_seeding),
|
|
689
|
+
("port_type", port_type),
|
|
690
|
+
("create_pin_group", create_pin_group),
|
|
691
|
+
):
|
|
692
|
+
if value is not None:
|
|
693
|
+
setattr(cfg, attr, value)
|
|
694
|
+
|
|
695
|
+
# List / container overrides
|
|
696
|
+
if signal_nets is not None:
|
|
697
|
+
cfg.signal_nets = signal_nets
|
|
698
|
+
if power_nets is not None:
|
|
699
|
+
cfg.power_nets = power_nets
|
|
700
|
+
if reference_net is not None:
|
|
701
|
+
cfg.reference_net = reference_net
|
|
702
|
+
if batch_groups is not None:
|
|
703
|
+
cfg.batch_groups = batch_groups
|
|
704
|
+
if components is not None:
|
|
705
|
+
cfg.components = components
|
|
706
|
+
if solder_balls is not None:
|
|
707
|
+
cfg.solder_balls = solder_balls
|
|
708
|
+
if simulation_setup is not None:
|
|
709
|
+
cfg.simulation_setup = simulation_setup
|
|
710
|
+
|
|
711
|
+
return cfg
|