pyedb 0.58.0__py3-none-any.whl → 0.59.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 +1 -1
- pyedb/dotnet/database/components.py +37 -39
- pyedb/dotnet/database/general.py +3 -2
- pyedb/dotnet/database/sim_setup_data/data/settings.py +2 -2
- pyedb/grpc/edb.py +7 -7
- pyedb/grpc/edb_init.py +7 -19
- pyedb/workflows/sipi/hfss_auto_configuration.py +711 -0
- {pyedb-0.58.0.dist-info → pyedb-0.59.0.dist-info}/METADATA +1 -1
- {pyedb-0.58.0.dist-info → pyedb-0.59.0.dist-info}/RECORD +11 -10
- {pyedb-0.58.0.dist-info → pyedb-0.59.0.dist-info}/WHEEL +0 -0
- {pyedb-0.58.0.dist-info → pyedb-0.59.0.dist-info}/licenses/LICENSE +0 -0
pyedb/__init__.py
CHANGED
|
@@ -1011,49 +1011,47 @@ class Components(object):
|
|
|
1011
1011
|
return False
|
|
1012
1012
|
pad_params = self._padstack.get_pad_parameters(pin=cmp_pins[0], layername=pin_layers[0], pad_type=0)
|
|
1013
1013
|
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
sball_diam = min([self._pedb.edb_value(val).ToDouble() for val in pad_params[1]])
|
|
1026
|
-
sball_mid_diam = sball_diam
|
|
1027
|
-
else: # pragma no cover
|
|
1028
|
-
sball_diam = solder_balls_size
|
|
1029
|
-
if solder_balls_mid_size:
|
|
1030
|
-
sball_mid_diam = solder_balls_mid_size
|
|
1031
|
-
else:
|
|
1032
|
-
sball_mid_diam = solder_balls_size
|
|
1033
|
-
if not solder_balls_height: # pragma no cover
|
|
1034
|
-
solder_balls_height = 2 * sball_diam / 3
|
|
1014
|
+
if not solder_balls_height:
|
|
1015
|
+
solder_balls_height = self.instances[component.GetName()].solder_ball_height
|
|
1016
|
+
if not solder_balls_size:
|
|
1017
|
+
solder_balls_size = self.instances[component.GetName()].solder_ball_diameter[0]
|
|
1018
|
+
if not solder_balls_mid_size:
|
|
1019
|
+
solder_balls_mid_size = self.instances[component.GetName()].solder_ball_diameter[1]
|
|
1020
|
+
|
|
1021
|
+
if not pad_params[0] == 7:
|
|
1022
|
+
if not solder_balls_size: # pragma no cover
|
|
1023
|
+
sball_diam = min([self._pedb.edb_value(val).ToDouble() for val in pad_params[1]])
|
|
1024
|
+
sball_mid_diam = sball_diam
|
|
1035
1025
|
else: # pragma no cover
|
|
1036
|
-
|
|
1037
|
-
bbox = pad_params[1]
|
|
1038
|
-
sball_diam = min([abs(bbox[2] - bbox[0]), abs(bbox[3] - bbox[1])]) * 0.8
|
|
1039
|
-
else:
|
|
1040
|
-
sball_diam = solder_balls_size
|
|
1041
|
-
if not solder_balls_height:
|
|
1042
|
-
solder_balls_height = 2 * sball_diam / 3
|
|
1026
|
+
sball_diam = solder_balls_size
|
|
1043
1027
|
if solder_balls_mid_size:
|
|
1044
1028
|
sball_mid_diam = solder_balls_mid_size
|
|
1045
1029
|
else:
|
|
1046
|
-
sball_mid_diam =
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1030
|
+
sball_mid_diam = solder_balls_size
|
|
1031
|
+
if not solder_balls_height: # pragma no cover
|
|
1032
|
+
solder_balls_height = 2 * sball_diam / 3
|
|
1033
|
+
else: # pragma no cover
|
|
1034
|
+
if not solder_balls_size:
|
|
1035
|
+
bbox = pad_params[1]
|
|
1036
|
+
sball_diam = min([abs(bbox[2] - bbox[0]), abs(bbox[3] - bbox[1])]) * 0.8
|
|
1037
|
+
else:
|
|
1038
|
+
sball_diam = solder_balls_size
|
|
1039
|
+
if not solder_balls_height:
|
|
1040
|
+
solder_balls_height = 2 * sball_diam / 3
|
|
1041
|
+
if solder_balls_mid_size:
|
|
1042
|
+
sball_mid_diam = solder_balls_mid_size
|
|
1043
|
+
else:
|
|
1044
|
+
sball_mid_diam = sball_diam
|
|
1045
|
+
sball_shape = "Cylinder"
|
|
1046
|
+
if not sball_diam == sball_mid_diam:
|
|
1047
|
+
sball_shape = "Spheroid"
|
|
1048
|
+
self.set_solder_ball(
|
|
1049
|
+
component=component,
|
|
1050
|
+
sball_height=solder_balls_height,
|
|
1051
|
+
sball_diam=sball_diam,
|
|
1052
|
+
sball_mid_diam=sball_mid_diam,
|
|
1053
|
+
shape=sball_shape,
|
|
1054
|
+
)
|
|
1057
1055
|
|
|
1058
1056
|
for pin in cmp_pins:
|
|
1059
1057
|
self._padstack.create_coax_port(padstackinstance=pin, name=port_name)
|
pyedb/dotnet/database/general.py
CHANGED
|
@@ -579,14 +579,14 @@ class ViaSettings(object):
|
|
|
579
579
|
-------
|
|
580
580
|
bool
|
|
581
581
|
"""
|
|
582
|
-
if float(self._parent._pedb.
|
|
582
|
+
if float(self._parent._pedb.version) < 2024.1:
|
|
583
583
|
self._parent._pedb.logger.error("Property only supported on Ansys release 2024R1 and later")
|
|
584
584
|
return False
|
|
585
585
|
return self._via_settings.ViaMeshPlating
|
|
586
586
|
|
|
587
587
|
@via_mesh_plating.setter
|
|
588
588
|
def via_mesh_plating(self, value):
|
|
589
|
-
if float(self._parent._pedb.
|
|
589
|
+
if float(self._parent._pedb.version) < 2024.1:
|
|
590
590
|
self._parent._pedb.logger.error("Property only supported on Ansys release 2024R1 and later")
|
|
591
591
|
else:
|
|
592
592
|
self._via_settings.ViaMeshPlating = value
|
pyedb/grpc/edb.py
CHANGED
|
@@ -217,8 +217,8 @@ class Edb(EdbInit):
|
|
|
217
217
|
self.standalone = True
|
|
218
218
|
self.oproject = oproject
|
|
219
219
|
self._main = sys.modules["__main__"]
|
|
220
|
-
self.
|
|
221
|
-
if not float(self.
|
|
220
|
+
self.version = edbversion
|
|
221
|
+
if not float(self.version) >= 2025.2:
|
|
222
222
|
raise "EDB gRPC is only supported with ANSYS release 2025R2 and higher."
|
|
223
223
|
self.logger.info("Using PyEDB with gRPC as Beta until ANSYS 2025R2 official release.")
|
|
224
224
|
self.isaedtowned = isaedtowned
|
|
@@ -620,7 +620,7 @@ class Edb(EdbInit):
|
|
|
620
620
|
if self.db.is_null:
|
|
621
621
|
self.logger.warning("Error Opening db")
|
|
622
622
|
self._active_cell = None
|
|
623
|
-
self.logger.info(f"Database {os.path.split(self.edbpath)[-1]} Opened in {self.
|
|
623
|
+
self.logger.info(f"Database {os.path.split(self.edbpath)[-1]} Opened in {self.version}")
|
|
624
624
|
self._active_cell = None
|
|
625
625
|
if self.cellname:
|
|
626
626
|
for cell in self.active_db.circuit_cells:
|
|
@@ -1371,7 +1371,7 @@ class Edb(EdbInit):
|
|
|
1371
1371
|
Layer filter file.
|
|
1372
1372
|
"""
|
|
1373
1373
|
control_file_temp = os.path.join(tempfile.gettempdir(), os.path.split(inputGDS)[-1][:-3] + "xml")
|
|
1374
|
-
if float(self.
|
|
1374
|
+
if float(self.version) < 2024.1:
|
|
1375
1375
|
if not is_linux and tech_file:
|
|
1376
1376
|
self.logger.error("Technology files are supported only in Linux. Use control file instead.")
|
|
1377
1377
|
return False
|
|
@@ -3062,7 +3062,7 @@ class Edb(EdbInit):
|
|
|
3062
3062
|
if name in self.setups:
|
|
3063
3063
|
self.logger.error("Setup name already used in the layout")
|
|
3064
3064
|
return False
|
|
3065
|
-
version = self.
|
|
3065
|
+
version = self.version.split(".")
|
|
3066
3066
|
if int(version[0]) >= 2024 and int(version[-1]) >= 2 or int(version[0]) > 2024:
|
|
3067
3067
|
setup = GrpcRaptorXSimulationSetup.create(cell=self.active_cell, name=name)
|
|
3068
3068
|
return RaptorXSimulationSetup(self, setup)
|
|
@@ -3253,7 +3253,7 @@ class Edb(EdbInit):
|
|
|
3253
3253
|
defined_ports = {}
|
|
3254
3254
|
project_connexions = None
|
|
3255
3255
|
for edb_path, zone_info in zone_dict.items():
|
|
3256
|
-
edb = Edb(edbversion=self.
|
|
3256
|
+
edb = Edb(edbversion=self.version, edbpath=edb_path)
|
|
3257
3257
|
edb.cutout(
|
|
3258
3258
|
use_pyaedt_cutout=True,
|
|
3259
3259
|
custom_extent=zone_info[1],
|
|
@@ -3795,7 +3795,7 @@ class Edb(EdbInit):
|
|
|
3795
3795
|
"No padstack instances found inside evaluated voids during model creation for arbitrary waveports"
|
|
3796
3796
|
)
|
|
3797
3797
|
return False
|
|
3798
|
-
cloned_edb = Edb(edbpath=output_edb, edbversion=self.
|
|
3798
|
+
cloned_edb = Edb(edbpath=output_edb, edbversion=self.version, restart_rpc_server=True)
|
|
3799
3799
|
|
|
3800
3800
|
cloned_edb.stackup.add_layer(
|
|
3801
3801
|
layer_name="ports",
|
pyedb/grpc/edb_init.py
CHANGED
|
@@ -43,23 +43,23 @@ class EdbInit(object):
|
|
|
43
43
|
def __init__(self, edbversion):
|
|
44
44
|
self.logger = settings.logger
|
|
45
45
|
self._db = None
|
|
46
|
-
self.
|
|
46
|
+
self.version = edbversion
|
|
47
47
|
self.logger.info("Logger is initialized in EDB.")
|
|
48
48
|
self.logger.info("legacy v%s", __version__)
|
|
49
49
|
self.logger.info("Python version %s", sys.version)
|
|
50
50
|
self.session = None
|
|
51
51
|
if is_linux:
|
|
52
|
-
if env_value(self.
|
|
53
|
-
self.base_path = env_path(self.
|
|
52
|
+
if env_value(self.version) in os.environ:
|
|
53
|
+
self.base_path = env_path(self.version)
|
|
54
54
|
sys.path.append(self.base_path)
|
|
55
55
|
else:
|
|
56
56
|
edb_path = os.getenv("PYAEDT_SERVER_AEDT_PATH")
|
|
57
57
|
if edb_path:
|
|
58
58
|
self.base_path = edb_path
|
|
59
59
|
sys.path.append(edb_path)
|
|
60
|
-
os.environ[env_value(self.
|
|
60
|
+
os.environ[env_value(self.version)] = self.base_path
|
|
61
61
|
else:
|
|
62
|
-
self.base_path = env_path(self.
|
|
62
|
+
self.base_path = env_path(self.version)
|
|
63
63
|
sys.path.append(self.base_path)
|
|
64
64
|
os.environ["ECAD_TRANSLATORS_INSTALL_DIR"] = self.base_path
|
|
65
65
|
oa_directory = os.path.join(self.base_path, "common", "oa")
|
|
@@ -100,7 +100,7 @@ class EdbInit(object):
|
|
|
100
100
|
"""
|
|
101
101
|
if not RpcSession.pid:
|
|
102
102
|
RpcSession.start(
|
|
103
|
-
edb_version=self.
|
|
103
|
+
edb_version=self.version,
|
|
104
104
|
port=port,
|
|
105
105
|
restart_server=restart_rpc_server,
|
|
106
106
|
)
|
|
@@ -133,7 +133,7 @@ class EdbInit(object):
|
|
|
133
133
|
RpcSession.pid = 0
|
|
134
134
|
if not RpcSession.pid:
|
|
135
135
|
RpcSession.start(
|
|
136
|
-
edb_version=self.
|
|
136
|
+
edb_version=self.version,
|
|
137
137
|
port=port,
|
|
138
138
|
restart_server=restart_rpc_server,
|
|
139
139
|
)
|
|
@@ -372,18 +372,6 @@ class EdbInit(object):
|
|
|
372
372
|
"""
|
|
373
373
|
self._db.import_material_from_control_file(control_file, schema_dir, append)
|
|
374
374
|
|
|
375
|
-
@property
|
|
376
|
-
def version(self):
|
|
377
|
-
"""Get version of the Database.
|
|
378
|
-
|
|
379
|
-
Returns
|
|
380
|
-
-------
|
|
381
|
-
tuple(int, int)
|
|
382
|
-
A tuple of the version numbers [major, minor]
|
|
383
|
-
"""
|
|
384
|
-
major, minor = self._db.version
|
|
385
|
-
return major, minor
|
|
386
|
-
|
|
387
375
|
def scale(self, scale_factor):
|
|
388
376
|
"""Uniformly scale all geometry and their locations by a positive factor.
|
|
389
377
|
|
|
@@ -0,0 +1,711 @@
|
|
|
1
|
+
# Copyright (C) 2023 - 2024 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
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
pyedb/__init__.py,sha256=
|
|
1
|
+
pyedb/__init__.py,sha256=JmPrpEANPckijk8EAsOXA4NmGyWhmSeSHJJ_tPPBYEg,1327
|
|
2
2
|
pyedb/edb_logger.py,sha256=ySZ_cZFJ09s1k3n3ft2q-MjaxH5pUwq18HNdiikTJNM,14497
|
|
3
3
|
pyedb/exceptions.py,sha256=iStxCzoIOnZH1JWUi1sE9bmCwa8fY1Mg55-aURn9boc,111
|
|
4
4
|
pyedb/siwave.py,sha256=4ttgMbOBnnCMtyNmU8SbOxkA7Cf0c_lV9cawEtq6V1k,17736
|
|
@@ -30,8 +30,8 @@ pyedb/dotnet/clr_module.py,sha256=AEy172pzZMwqbENX1RUTEkEm5viVNGd4Ti9KjSL3UAc,54
|
|
|
30
30
|
pyedb/dotnet/edb.py,sha256=UwybReXR8CdR3X4w9tZcPgT4jFVh1oD7MDGBGFt-X2s,194043
|
|
31
31
|
pyedb/dotnet/database/Variables.py,sha256=YqPk4iReyo-xNmWJ3s26_J35PImA9R0kznl2grzoY1c,78441
|
|
32
32
|
pyedb/dotnet/database/__init__.py,sha256=nIRLJ8VZLcMAp12zmGsnZ5x2BEEl7q_Kj_KAOXxVjpQ,52
|
|
33
|
-
pyedb/dotnet/database/components.py,sha256=
|
|
34
|
-
pyedb/dotnet/database/general.py,sha256=
|
|
33
|
+
pyedb/dotnet/database/components.py,sha256=xG0xJmLy5bPzz4MGDU4_5qYDStIdrDVPaytZbvzBsrw,111533
|
|
34
|
+
pyedb/dotnet/database/general.py,sha256=aKcVyYdQOWsALAH_12RN-l9UKy-ExOoZ88aGikx5iWU,4693
|
|
35
35
|
pyedb/dotnet/database/hfss.py,sha256=zkoqdK68T-QJEQwmJpEE-ta7qKIYLkLNMx1YCvk2Fvw,69079
|
|
36
36
|
pyedb/dotnet/database/layout_obj_instance.py,sha256=se6eJ2kfQOAZfAwObCBdr0A7CCD3st4aiPPVJR9eQoA,1407
|
|
37
37
|
pyedb/dotnet/database/layout_validation.py,sha256=fkOAircLkUhIItoY6V78wVQSgmcfDBFVUMXB2jCN4dI,13921
|
|
@@ -97,7 +97,7 @@ pyedb/dotnet/database/sim_setup_data/__init__.py,sha256=8jByHkoaowAYQTCww-zRrTQm
|
|
|
97
97
|
pyedb/dotnet/database/sim_setup_data/data/__init__.py,sha256=8jByHkoaowAYQTCww-zRrTQmN061fLz_OHjTLSrzQQY,58
|
|
98
98
|
pyedb/dotnet/database/sim_setup_data/data/adaptive_frequency_data.py,sha256=tlHI7PUUoseNnJAtihpjb1PwXYNr-4ztAAnunlLLWVQ,2463
|
|
99
99
|
pyedb/dotnet/database/sim_setup_data/data/mesh_operation.py,sha256=WqDQjs1yA8MDX0PtybjDEelvnn5B9L2xIts0BH4Y9dw,8775
|
|
100
|
-
pyedb/dotnet/database/sim_setup_data/data/settings.py,sha256=
|
|
100
|
+
pyedb/dotnet/database/sim_setup_data/data/settings.py,sha256=IrFy5Y8FLsBLnw-YRfk-OMw8JqNn8OgXBwcLGG3U4xo,30076
|
|
101
101
|
pyedb/dotnet/database/sim_setup_data/data/sim_setup_info.py,sha256=hN2TeXa8dbtOmEtwobhKuwomJXYs8cSJum3HQofuW3Y,4479
|
|
102
102
|
pyedb/dotnet/database/sim_setup_data/data/simulation_settings.py,sha256=ag-nl1gwKlNJOb3y7fBMSoSEwbUG_rwLzM25jLp8ado,10727
|
|
103
103
|
pyedb/dotnet/database/sim_setup_data/data/siw_dc_ir_settings.py,sha256=FnvDY1oxpnPo0EYMVXT7yfW-e-W3_NOGnvUvQYta4Ls,8627
|
|
@@ -126,8 +126,8 @@ pyedb/generic/plot.py,sha256=7xrCl0OlTXM8uhS6PoyB01OaEXsMmC6xfP3TiV5zguo,4228
|
|
|
126
126
|
pyedb/generic/process.py,sha256=KviVNw9KP-iB5QIY6WMZ0sBErcQbLfxsXJjlCwHo9Yc,12163
|
|
127
127
|
pyedb/generic/settings.py,sha256=nTVTEvZJOrMSXpIYmmHKKCiVUvECL_aiHcQ3BSX2HMw,12122
|
|
128
128
|
pyedb/grpc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
129
|
-
pyedb/grpc/edb.py,sha256=
|
|
130
|
-
pyedb/grpc/edb_init.py,sha256=
|
|
129
|
+
pyedb/grpc/edb.py,sha256=bNidAue3vL4UpvQK89-fx1-KGio3jeHEIEBBjjGY7IU,152754
|
|
130
|
+
pyedb/grpc/edb_init.py,sha256=dXepudKz0R65cjIG2qchap-nYCEYW2gYtfwn3P58YUI,15447
|
|
131
131
|
pyedb/grpc/rpc_session.py,sha256=espO-OFMeGi4Gms4DZAhnef7LaSvzpLmRI0kQu3ul4c,7157
|
|
132
132
|
pyedb/grpc/database/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
133
133
|
pyedb/grpc/database/_typing.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -299,7 +299,8 @@ pyedb/siwave_core/icepak.py,sha256=WnZ-t8mik7LDY06V8hZFV-TxRZJQWK7bu_8Ichx-oBs,5
|
|
|
299
299
|
pyedb/siwave_core/product_properties.py,sha256=m7HIMeYKJZqfzWbJklEOKqi3KJHwhj7W0SRbkRCng_c,5660
|
|
300
300
|
pyedb/siwave_core/cpa/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
301
301
|
pyedb/siwave_core/cpa/simulation_setup_data_model.py,sha256=hQsDCvfSDGv3kdDdkTjJYlQqrP1mT4_-_sR0_iQFxi8,5577
|
|
302
|
-
pyedb
|
|
303
|
-
pyedb-0.
|
|
304
|
-
pyedb-0.
|
|
305
|
-
pyedb-0.
|
|
302
|
+
pyedb/workflows/sipi/hfss_auto_configuration.py,sha256=FIlsuYyt5HicdRRs6CxJsWWZWSFfDdeMWlxD9MwWQKo,28307
|
|
303
|
+
pyedb-0.59.0.dist-info/licenses/LICENSE,sha256=qQWivZ12ETN5l3QxvTARY-QI5eoRRlyHdwLlAj0Bg5I,1089
|
|
304
|
+
pyedb-0.59.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
305
|
+
pyedb-0.59.0.dist-info/METADATA,sha256=doDDMsPzXJUgBs0UHs12cLAtq_9GtUS3-oanIbLBTkY,8653
|
|
306
|
+
pyedb-0.59.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|