pyedb 0.48.0__py3-none-any.whl → 0.50.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.

Files changed (40) hide show
  1. pyedb/__init__.py +1 -1
  2. pyedb/configuration/cfg_modeler.py +42 -11
  3. pyedb/configuration/cfg_ports_sources.py +9 -1
  4. pyedb/configuration/cfg_stackup.py +4 -4
  5. pyedb/dotnet/database/cell/hierarchy/component.py +64 -8
  6. pyedb/dotnet/database/cell/hierarchy/s_parameter_model.py +7 -0
  7. pyedb/dotnet/database/cell/terminal/terminal.py +3 -3
  8. pyedb/dotnet/database/components.py +68 -46
  9. pyedb/dotnet/database/definition/package_def.py +29 -5
  10. pyedb/dotnet/database/edb_data/padstacks_data.py +5 -5
  11. pyedb/dotnet/database/edb_data/primitives_data.py +3 -3
  12. pyedb/dotnet/database/edb_data/variables.py +3 -3
  13. pyedb/dotnet/database/geometry/polygon_data.py +14 -1
  14. pyedb/dotnet/database/materials.py +16 -16
  15. pyedb/dotnet/database/modeler.py +50 -9
  16. pyedb/dotnet/database/padstack.py +7 -5
  17. pyedb/dotnet/database/sim_setup_data/data/settings.py +28 -0
  18. pyedb/dotnet/database/siwave.py +36 -32
  19. pyedb/dotnet/database/stackup.py +1 -0
  20. pyedb/dotnet/database/utilities/hfss_simulation_setup.py +5 -6
  21. pyedb/dotnet/database/utilities/siwave_simulation_setup.py +3 -1
  22. pyedb/dotnet/edb.py +22 -20
  23. pyedb/extensions/__init__.py +0 -0
  24. pyedb/extensions/via_design_backend.py +681 -0
  25. pyedb/grpc/database/components.py +57 -48
  26. pyedb/grpc/database/definition/materials.py +33 -33
  27. pyedb/grpc/database/definitions.py +6 -4
  28. pyedb/grpc/database/hfss.py +2 -2
  29. pyedb/grpc/database/hierarchy/component.py +3 -2
  30. pyedb/grpc/database/layers/stackup_layer.py +119 -22
  31. pyedb/grpc/database/layout_validation.py +5 -5
  32. pyedb/grpc/database/primitive/path.py +1 -1
  33. pyedb/grpc/database/source_excitations.py +156 -0
  34. pyedb/grpc/database/stackup.py +50 -24
  35. pyedb/grpc/edb.py +115 -105
  36. {pyedb-0.48.0.dist-info → pyedb-0.50.0.dist-info}/METADATA +1 -1
  37. {pyedb-0.48.0.dist-info → pyedb-0.50.0.dist-info}/RECORD +39 -38
  38. pyedb/extensions/pre_layout_design_toolkit/via_design.py +0 -1151
  39. {pyedb-0.48.0.dist-info → pyedb-0.50.0.dist-info}/LICENSE +0 -0
  40. {pyedb-0.48.0.dist-info → pyedb-0.50.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,681 @@
1
+ from copy import deepcopy as copy
2
+ import json
3
+ from pathlib import Path
4
+ import re
5
+ import tempfile
6
+ from typing import Union
7
+
8
+ import numpy as np
9
+ import pandas as pd
10
+ import toml
11
+
12
+ from pyedb import Edb
13
+
14
+
15
+ def create_variable(obj, name_suffix, value):
16
+ var_name = f"{obj.name}_{name_suffix}"
17
+ var_value = value
18
+ obj.variables.append(
19
+ {"name": var_name, "value": var_value, "description": f"Net name = {obj.net_name}"},
20
+ )
21
+ return var_name
22
+
23
+
24
+ class StitchingVias:
25
+ def __init__(self, p_via, start_angle, step_angle, number_of_vias, distance, clockwise=True):
26
+ self.p_via = p_via
27
+ self.name = f"{self.p_via.name}_stitching_via"
28
+ self.start_angle = int(start_angle)
29
+ self.step_angle = int(step_angle)
30
+ self.number_of_vias = int(number_of_vias)
31
+ self.distance = distance
32
+ self.clockwise = clockwise
33
+
34
+ self.vias = []
35
+ for idx, angle in enumerate(
36
+ np.arange(self.start_angle, start_angle + self.step_angle * self.number_of_vias, self.step_angle)
37
+ ):
38
+ dx = f"cos({angle}deg)*({self.distance}+{self.p_via.anti_pad_diameter}/2)"
39
+ dy = f"sin({angle}deg)*({self.distance}+{self.p_via.anti_pad_diameter}/2)"
40
+ via = GroundVia(
41
+ p_signal=self.p_via.p_signal,
42
+ name=f"{self.name}_{idx}",
43
+ net_name="GND",
44
+ padstack_def=self.p_via.padstack_def,
45
+ start_layer=self.p_via.start_layer,
46
+ stop_layer=self.p_via.stop_layer,
47
+ base_x=self.p_via.x,
48
+ base_y=self.p_via.y,
49
+ dx=dx,
50
+ dy=dy,
51
+ flip_dx=self.p_via.flip_dx,
52
+ flip_dy=self.p_via.flip_dy,
53
+ connection_trace=False,
54
+ with_solder_ball=False,
55
+ backdrill_parameters=None,
56
+ conductor_layers=self.p_via.p_signal.p_board.conductor_layers,
57
+ )
58
+ self.vias.append(via)
59
+
60
+ def populate_config(self, cfg):
61
+ for via in self.vias:
62
+ via.populate_config(cfg)
63
+
64
+
65
+ class Trace:
66
+ def __init__(
67
+ self,
68
+ p_via,
69
+ name,
70
+ net_name,
71
+ layer,
72
+ width,
73
+ clearance,
74
+ incremental_path: list[list],
75
+ flip_dx,
76
+ flip_dy,
77
+ end_cap_style,
78
+ port: Union[dict, None],
79
+ ):
80
+ self.p_via = p_via
81
+ self.variables = []
82
+ self.name = name
83
+ self.net_name = net_name
84
+ self.layer = layer
85
+ self.width = create_variable(self, "width", width)
86
+ self.clearance = create_variable(self, "clearance", clearance)
87
+ self.flip_dx = flip_dx
88
+ self.flip_dy = flip_dy
89
+ self.end_cap_style = end_cap_style
90
+ self.port = port
91
+
92
+ # self.voids = []
93
+
94
+ self.incremental_path = [
95
+ i if idx == 0 else [f"{i[0]}*{-1 if self.flip_dx else 1}", f"{i[1]}*{-1 if self.flip_dy else 1}"]
96
+ for idx, i in enumerate(incremental_path)
97
+ ]
98
+ self.incremental_path = []
99
+ for idx, i in enumerate(incremental_path):
100
+ if idx == 0:
101
+ self.incremental_path.append(i)
102
+ else:
103
+ dx = create_variable(self, name_suffix=f"_dx_{idx}", value=i[0])
104
+ dy = create_variable(self, name_suffix=f"_dy_{idx}", value=i[1])
105
+ temp = [f"{dx}*{-1 if self.flip_dx else 1}", f"{dy}*{-1 if self.flip_dy else 1}"]
106
+ self.incremental_path.append(temp)
107
+
108
+ self.path = [self.incremental_path[0]]
109
+ x, y = self.incremental_path[0]
110
+ for x0, y0 in self.incremental_path[1:]:
111
+ x = f"{x}+({x0})"
112
+ y = f"{y}+({y0})"
113
+ self.path.append([x, y])
114
+
115
+ def populate_config(self, cfg):
116
+ cfg["variables"].extend(self.variables)
117
+ trace = {
118
+ "name": self.name,
119
+ "layer": self.layer,
120
+ "width": self.width,
121
+ # "incremental_path": self.incremental_path,
122
+ "path": self.path,
123
+ "net_name": self.net_name,
124
+ "start_cap_style": "round",
125
+ "end_cap_style": self.end_cap_style,
126
+ "corner_style": "round",
127
+ }
128
+ cfg["modeler"]["traces"].append(trace)
129
+
130
+ trace_void = copy(trace)
131
+ trace_void["name"] = f"{self.name}_void"
132
+ trace_void["width"] = f"{self.width}+2*{self.clearance}"
133
+ trace_void["end_cap_style"] = "round"
134
+ cfg["modeler"]["traces"].append(trace_void)
135
+ self.p_via.p_signal.p_board.voids.append(trace_void)
136
+ # self.voids.append(trace_void)
137
+
138
+ if self.port is not None:
139
+ port = self.get_port_cfg()
140
+ cfg["ports"].append(port)
141
+
142
+ def get_port_cfg(self):
143
+ return {
144
+ "name": f"port_{self.name}",
145
+ "type": "wave_port",
146
+ "primitive_name": self.name,
147
+ "point_on_edge": self.path[-1],
148
+ "horizontal_extent_factor": self.port["horizontal_extent_factor"],
149
+ "vertical_extent_factor": self.port["vertical_extent_factor"],
150
+ "pec_launch_width": "0.02mm",
151
+ }
152
+
153
+
154
+ class GroundVia:
155
+ @property
156
+ def x(self):
157
+ return f"{self.base_x}+{self.dx}"
158
+
159
+ @property
160
+ def y(self):
161
+ return f"{self.base_y}+{self.dy}"
162
+
163
+ def __init__(
164
+ self,
165
+ p_signal,
166
+ name,
167
+ net_name,
168
+ padstack_def,
169
+ start_layer,
170
+ stop_layer,
171
+ base_x,
172
+ base_y,
173
+ dx,
174
+ dy,
175
+ flip_dx,
176
+ flip_dy,
177
+ connection_trace: Union[dict, Trace],
178
+ with_solder_ball,
179
+ backdrill_parameters,
180
+ conductor_layers: list,
181
+ **kwargs,
182
+ ):
183
+ self.p_signal = p_signal
184
+ self.variables = []
185
+ self.name = name
186
+ self.net_name = net_name
187
+ self.padstack_def = padstack_def
188
+ self.start_layer = start_layer
189
+ self.stop_layer = stop_layer
190
+ self.base_x = base_x
191
+ self.base_y = base_y
192
+
193
+ var_dx = create_variable(self, "dx", dx)
194
+ var_dy = create_variable(self, "dy", dy)
195
+ self.flip_dx = flip_dx
196
+ self.flip_dy = flip_dy
197
+ self.dx = var_dx if flip_dx is False else f"-1*({var_dx})"
198
+ self.dy = var_dy if flip_dy is False else f"-1*({var_dy})"
199
+ self.with_solder_ball = with_solder_ball
200
+ self.backdrill_parameters = backdrill_parameters
201
+ self.conductor_layers = conductor_layers
202
+
203
+ self.traces = []
204
+ self.fanout_traces = []
205
+ # self._voids = []
206
+
207
+ if connection_trace is not False:
208
+ trace = Trace(
209
+ p_via=self,
210
+ name=f"{self.name}_trace",
211
+ net_name=self.net_name,
212
+ layer=self.stop_layer,
213
+ width=connection_trace["width"],
214
+ clearance=connection_trace["clearance"],
215
+ incremental_path=[[base_x, base_y], [var_dx, var_dy]],
216
+ flip_dx=flip_dx,
217
+ flip_dy=flip_dy,
218
+ end_cap_style="round",
219
+ port=None,
220
+ )
221
+ self.traces.append(trace)
222
+
223
+ def populate_config(self, cfg):
224
+ cfg["variables"].extend(self.variables)
225
+
226
+ for trace in self.traces:
227
+ trace.populate_config(cfg)
228
+
229
+ for trace in self.fanout_traces:
230
+ trace.populate_config(cfg)
231
+
232
+ padstack_instance = {
233
+ "name": self.name,
234
+ "definition": self.padstack_def,
235
+ "layer_range": [self.start_layer, self.stop_layer],
236
+ "position": [self.x, self.y],
237
+ "net_name": self.net_name,
238
+ }
239
+ if self.with_solder_ball:
240
+ padstack_instance["solder_ball_layer"] = self.start_layer
241
+ padstack_instance["layer_range"] = [self.stop_layer, self.stop_layer]
242
+
243
+ padstack_instance_upper = copy(padstack_instance)
244
+ padstack_instance_upper["layer_range"] = [self.start_layer, self.start_layer]
245
+ if self.backdrill_parameters is not False:
246
+ padstack_instance["backdrill_parameters"] = self.backdrill_parameters
247
+
248
+ cfg["modeler"]["padstack_instances"].append(padstack_instance)
249
+
250
+
251
+ class Via(GroundVia):
252
+ def __init__(
253
+ self, anti_pad_diameter, fanout_trace: list[Union[dict, Trace]], stitching_vias: Union[dict, None], **kwargs
254
+ ):
255
+ super().__init__(**kwargs)
256
+
257
+ self.anti_pad_diameter = create_variable(self, "anti_pad_diameter", anti_pad_diameter)
258
+ for t in fanout_trace:
259
+ layer = t["layer"]
260
+
261
+ incremental_path = copy([[self.x, self.y]])
262
+ incremental_path.extend(t["incremental_path"])
263
+ t_flip_dx = t["flip_dx"]
264
+ t_flip_dy = t["flip_dy"]
265
+
266
+ trace = Trace(
267
+ p_via=self,
268
+ name=f"{self.net_name}_{layer}_fanout",
269
+ net_name=self.net_name,
270
+ layer=layer,
271
+ width=t["width"],
272
+ clearance=t["clearance"],
273
+ incremental_path=incremental_path,
274
+ flip_dx=self.flip_dx ^ t_flip_dx,
275
+ flip_dy=self.flip_dy ^ t_flip_dy,
276
+ end_cap_style=t["end_cap_style"],
277
+ port=t["port"],
278
+ )
279
+ self.fanout_traces.append(trace)
280
+ self.stitching_vias = StitchingVias(self, **stitching_vias) if stitching_vias is not False else False
281
+
282
+ def populate_config(self, cfg):
283
+ super().populate_config(cfg)
284
+ if self.start_layer == self.stop_layer:
285
+ anti_pad = {
286
+ "type": "circle",
287
+ "name": f"{self.name}_anti_pad_{self.start_layer}",
288
+ "layer": self.start_layer,
289
+ "net_name": self.net_name,
290
+ "position": [self.x, self.y],
291
+ "radius": f"{self.anti_pad_diameter}/2",
292
+ }
293
+ cfg["modeler"]["planes"].append(anti_pad)
294
+ # self.voids.append(anti_pad)
295
+ self.p_signal.p_board.voids.append(anti_pad)
296
+ else:
297
+ start_layer_idx = self.conductor_layers.index(self.start_layer)
298
+ stop_layer_idx = self.conductor_layers.index(self.stop_layer)
299
+ for i in np.arange(start_layer_idx, stop_layer_idx + 1):
300
+ anti_pad = {
301
+ "type": "circle",
302
+ "name": f"{self.name}_anti_pad_{self.conductor_layers[i]}",
303
+ "layer": self.conductor_layers[i],
304
+ "net_name": self.net_name,
305
+ "position": [self.x, self.y],
306
+ "radius": f"{self.anti_pad_diameter}/2",
307
+ }
308
+ cfg["modeler"]["planes"].append(anti_pad)
309
+ # self.voids.append(anti_pad)
310
+ self.p_signal.p_board.voids.append(anti_pad)
311
+
312
+ if self.stitching_vias is not False:
313
+ self.stitching_vias.populate_config(cfg)
314
+
315
+
316
+ class Signal:
317
+ """vias and traces."""
318
+
319
+ def __init__(
320
+ self,
321
+ p_board,
322
+ signal_name,
323
+ name_suffix: Union[None, str],
324
+ base_x,
325
+ base_y,
326
+ stacked_vias,
327
+ flip_x,
328
+ flip_y,
329
+ ):
330
+ self.p_board = p_board
331
+ self.net_name = signal_name if name_suffix is None else f"{signal_name}_{name_suffix}"
332
+ self.name_suffix = name_suffix
333
+ self.base_x = base_x
334
+ self.base_y = base_y
335
+
336
+ self.vias = []
337
+ x = self.base_x
338
+ y = self.base_y
339
+ for v_idx, i in enumerate(stacked_vias):
340
+ dx = i["dx"]
341
+ dy = i["dy"]
342
+
343
+ connection_trace = i["connection_trace"]
344
+ start_layer = i["start_layer"]
345
+ stop_layer = i["stop_layer"]
346
+
347
+ flip_x_1 = not i["flip_dx"] if flip_x else i["flip_dx"]
348
+ flip_y_1 = not i["flip_dy"] if flip_y else i["flip_dy"]
349
+ if i["padstack_def"].startswith("BGA"):
350
+ flip_x_1 = False
351
+ flip_y_1 = False
352
+
353
+ if self.net_name.startswith("GND"):
354
+ via_class = GroundVia
355
+ name = f"{self.net_name}_{start_layer}_{stop_layer}_{v_idx}"
356
+ net_name = "GND"
357
+ else:
358
+ via_class = Via
359
+ name = f"{self.net_name}_{start_layer}_{stop_layer}"
360
+ net_name = self.net_name
361
+
362
+ via = via_class(
363
+ p_signal=self,
364
+ name=name,
365
+ net_name=net_name,
366
+ padstack_def=i["padstack_def"],
367
+ start_layer=start_layer,
368
+ stop_layer=stop_layer,
369
+ base_x=x,
370
+ base_y=y,
371
+ dx=dx,
372
+ dy=dy,
373
+ flip_dx=flip_x_1,
374
+ flip_dy=flip_y_1,
375
+ connection_trace=connection_trace,
376
+ with_solder_ball=i["with_solder_ball"],
377
+ backdrill_parameters=i["backdrill_parameters"],
378
+ conductor_layers=self.p_board.conductor_layers,
379
+ stitching_vias=i["stitching_vias"],
380
+ anti_pad_diameter=i["anti_pad_diameter"],
381
+ fanout_trace=i.get("fanout_trace", []),
382
+ )
383
+ x = via.x
384
+ y = via.y
385
+ self.vias.append(via)
386
+
387
+ def populate_config(self, cfg_modeler):
388
+ for i in self.vias:
389
+ i.populate_config(cfg_modeler)
390
+
391
+
392
+ class DiffSignal:
393
+ def __init__(self, p_board, name, signals, fanout_trace, stacked_vias):
394
+ self.p_board = p_board
395
+ self.name = name
396
+ self.signal_p_name, self.signal_n_name = signals
397
+ self.fanout_trace = fanout_trace
398
+ self.stacked_vias = stacked_vias
399
+ for i in self.stacked_vias:
400
+ i["fanout_trace"] = []
401
+
402
+ self.variables = []
403
+ # self.voids = []
404
+ self.diff_ports = []
405
+
406
+ p_x, p_y = self.p_board.get_signal_location(self.signal_p_name)[0]
407
+ n_x, n_y = self.p_board.get_signal_location(self.signal_n_name)[0]
408
+ p_x = f"{p_x}*pitch"
409
+ p_y = f"{p_y}*pitch"
410
+ n_x = f"{n_x}*pitch"
411
+ n_y = f"{n_y}*pitch"
412
+
413
+ vars_sep = {}
414
+ for trace in self.fanout_trace:
415
+ via_index = trace["via_index"]
416
+ trace2 = dict()
417
+ trace2["layer"] = trace["layer"]
418
+ trace2["width"] = trace["width"]
419
+ trace2["clearance"] = trace["clearance"]
420
+ trace2["flip_dx"] = trace["flip_dx"]
421
+ trace2["flip_dy"] = trace["flip_dy"]
422
+ trace2["end_cap_style"] = trace["end_cap_style"]
423
+ trace2["port"] = trace["port"]
424
+
425
+ incremental_path_dy = trace["incremental_path_dy"]
426
+ incremental_path = [[0, incremental_path_dy[0]], [0, incremental_path_dy[1]]]
427
+ trace2["incremental_path"] = incremental_path
428
+
429
+ self.stacked_vias[via_index]["fanout_trace"].append(trace2)
430
+
431
+ var_separation = f"{self.name}_{trace['layer']}_fanout_separation"
432
+ self.variables.append(
433
+ {"name": var_separation, "value": trace["separation"]},
434
+ )
435
+ vars_sep[trace["layer"]] = var_separation
436
+
437
+ stacked_vias_reversed = list(reversed(stacked_vias))
438
+
439
+ pcb_fanout_center = f"{p_x}+pitch/2"
440
+ pkg_fanout_center = f"{p_x}+pitch"
441
+ # fanout_x = f"{diff_center}-({var_separation})/2"
442
+
443
+ self.signal_p = Signal(
444
+ p_board=self.p_board,
445
+ signal_name=self.name,
446
+ name_suffix="P",
447
+ base_x=p_x,
448
+ base_y=p_y,
449
+ stacked_vias=stacked_vias_reversed,
450
+ flip_x=False,
451
+ flip_y=False,
452
+ )
453
+
454
+ for v in self.signal_p.vias:
455
+ for t in v.fanout_traces:
456
+ var_sep = vars_sep[t.layer]
457
+ if t.layer.startswith("PCB"):
458
+ t.path[1][0] = f"{pcb_fanout_center}-{var_sep}"
459
+ t.path[2][0] = f"{pcb_fanout_center}-{var_sep}"
460
+ else:
461
+ t.path[1][0] = f"{pkg_fanout_center}-{var_sep}"
462
+ t.path[2][0] = f"{pkg_fanout_center}-{var_sep}"
463
+
464
+ self.signal_n = Signal(
465
+ p_board=self.p_board,
466
+ signal_name=self.name,
467
+ name_suffix="N",
468
+ base_x=n_x,
469
+ base_y=n_y,
470
+ stacked_vias=stacked_vias_reversed,
471
+ flip_x=True,
472
+ flip_y=False,
473
+ )
474
+ for v in self.signal_n.vias:
475
+ for t in v.fanout_traces:
476
+ var_sep = vars_sep[t.layer]
477
+ if t.layer.startswith("PCB"):
478
+ t.path[1][0] = f"{pcb_fanout_center}+{var_sep}"
479
+ t.path[2][0] = f"{pcb_fanout_center}+{var_sep}"
480
+ else:
481
+ t.path[1][0] = f"{pkg_fanout_center}+{var_sep}"
482
+ t.path[2][0] = f"{pkg_fanout_center}+{var_sep}"
483
+
484
+ for v_idx, v in enumerate(self.signal_p.vias):
485
+ for t_idx, t_p in enumerate(v.fanout_traces):
486
+ port_p = t_p.get_port_cfg()
487
+ t_p.port = None
488
+ t_n = self.signal_n.vias[v_idx].fanout_traces[t_idx]
489
+ port_n = t_n.get_port_cfg()
490
+ t_n.port = None
491
+ pattern = r"^(.*)_([NPnp])_(.*)$"
492
+ m1 = re.match(pattern, port_p["name"])
493
+
494
+ diff_port = {
495
+ "name": f"{m1.group(1)}_{m1.group(3)}",
496
+ "type": "diff_wave_port",
497
+ "positive_terminal": {
498
+ "primitive_name": port_p["primitive_name"],
499
+ "point_on_edge": port_p["point_on_edge"],
500
+ },
501
+ "negative_terminal": {
502
+ "primitive_name": port_n["primitive_name"],
503
+ "point_on_edge": port_n["point_on_edge"],
504
+ },
505
+ "horizontal_extent_factor": port_p["horizontal_extent_factor"],
506
+ "vertical_extent_factor": port_n["vertical_extent_factor"],
507
+ "pec_launch_width": "0.02mm",
508
+ }
509
+ self.diff_ports.append(diff_port)
510
+
511
+ def populate_config(self, cfg):
512
+ cfg["variables"].extend(self.variables)
513
+ self.signal_p.populate_config(cfg)
514
+ # self.voids.extend(self.signal_p.voids)
515
+ self.signal_n.populate_config(cfg)
516
+ # self.voids.extend(self.signal_n.voids)
517
+ cfg["ports"].extend(self.diff_ports)
518
+
519
+
520
+ class Board:
521
+ @property
522
+ def conductor_layers(self):
523
+ return [i["name"] for i in self.stackup if i["type"] == "signal"]
524
+
525
+ def __init__(self, stackup, padstack_defs, outline_extent, pitch, pin_map, signals, differential_signals):
526
+ self.voids = []
527
+ self.variables = [{"name": "pitch", "value": pitch, "description": ""}]
528
+
529
+ self.stackup = stackup
530
+ self.padstack_defs = padstack_defs
531
+ self.outline_extent = outline_extent
532
+
533
+ self.pin_map = pin_map
534
+ self.signals = self.parser_signals(signals) if signals is not False else []
535
+ self.differential_signals = (
536
+ self.parser_differential_signals(differential_signals) if differential_signals is not False else []
537
+ )
538
+
539
+ def get_signal_location(self, signal_name):
540
+ pin_map = pd.DataFrame(self.pin_map)
541
+ temp = (pin_map == signal_name).stack()
542
+ xy = [[i[1], i[0]] for i in temp[temp].index.tolist()]
543
+ return xy
544
+
545
+ def parser_signals(self, data):
546
+ signals = []
547
+
548
+ for name, signal_data in data.items():
549
+ fanout = signal_data["fanout_trace"]
550
+ stacked_vias = signal_data["stacked_vias"]
551
+ for f in fanout:
552
+ idx = f["via_index"]
553
+ stacked_vias[idx]["fanout_trace"].append(f)
554
+
555
+ stacked_vias_reversed = list(reversed(stacked_vias))
556
+ for x, y in self.get_signal_location(name):
557
+ s = Signal(
558
+ p_board=self,
559
+ signal_name=name if name != "GND" else f"{name}_{x}{y}",
560
+ name_suffix=None,
561
+ base_x=f"{x}*pitch",
562
+ base_y=f"{y}*pitch",
563
+ stacked_vias=stacked_vias_reversed,
564
+ flip_x=False,
565
+ flip_y=False,
566
+ )
567
+ signals.append(s)
568
+ return signals
569
+
570
+ def parser_differential_signals(self, data):
571
+ diff_signals = []
572
+ for name, temp in data.items():
573
+ signals = temp["signals"]
574
+ fanout_trace = temp["fanout_trace"]
575
+ stacked_vias = temp["stacked_vias"]
576
+ diff_signal = DiffSignal(self, name, signals, fanout_trace, stacked_vias)
577
+ diff_signals.append(diff_signal)
578
+ return diff_signals
579
+
580
+ def populate_config(self, cfg):
581
+ cfg["variables"].extend(self.variables)
582
+
583
+ cfg["stackup"]["layers"] = self.stackup
584
+ for p in self.padstack_defs:
585
+ regular_pad = []
586
+ for layer in self.conductor_layers:
587
+ regular_pad.append(
588
+ {
589
+ "layer_name": layer,
590
+ "shape": "circle",
591
+ "diameter": p["pad_diameter"],
592
+ }
593
+ )
594
+ pdef = copy(p)
595
+ pdef["material"] = "copper"
596
+ pdef["hole_range"] = "upper_pad_to_lower_pad"
597
+ pdef["pad_parameters"] = {"regular_pad": regular_pad}
598
+ pdef["hole_parameters"] = {
599
+ "shape": "circle",
600
+ "diameter": p["hole_diameter"],
601
+ }
602
+
603
+ cfg["modeler"]["padstack_definitions"].append(pdef)
604
+
605
+ # voids = []
606
+ for signal in self.signals:
607
+ signal.populate_config(cfg)
608
+ # voids.extend(signal.voids)
609
+
610
+ for diff_signal in self.differential_signals:
611
+ diff_signal.populate_config(cfg)
612
+ # voids.extend(diff_signal.voids)
613
+
614
+ matrix = np.array(self.pin_map)
615
+ y_size_count, x_size_count = matrix.shape
616
+ x_lower_left = f"-1*({self.outline_extent})"
617
+ x_upper_right = f"({self.outline_extent})+({x_size_count}-1)*pitch"
618
+ y_lower_left = f"-1*({self.outline_extent})"
619
+ y_upper_right = f"({self.outline_extent})+({y_size_count}-1)*pitch"
620
+ for l in self.conductor_layers:
621
+ p = {
622
+ "type": "rectangle",
623
+ "name": f"GND_{l}",
624
+ "layer": l,
625
+ "net_name": "GND",
626
+ "lower_left_point": [x_lower_left, y_lower_left],
627
+ "upper_right_point": [x_upper_right, y_upper_right],
628
+ "voids": [],
629
+ }
630
+ for v in self.voids:
631
+ if v["layer"] == l:
632
+ p["voids"].append(v["name"])
633
+ cfg["modeler"]["planes"].append(p)
634
+
635
+
636
+ class ViaDesignBackend:
637
+ _OUTPUT_DIR = None
638
+
639
+ @property
640
+ def output_dir(self):
641
+ if self._OUTPUT_DIR is None:
642
+ output_dir = self.cfg["general"]["output_dir"]
643
+ if output_dir == "":
644
+ self._OUTPUT_DIR = Path(tempfile.TemporaryDirectory(suffix=".ansys").name)
645
+ else:
646
+ self._OUTPUT_DIR = Path(output_dir)
647
+ return self._OUTPUT_DIR
648
+
649
+ def __init__(self, cfg):
650
+ cfg_json = {
651
+ "stackup": {"layers": [], "materials": []},
652
+ "variables": [],
653
+ "ports": [],
654
+ "modeler": {"traces": [], "planes": [], "padstack_definitions": [], "padstack_instances": []},
655
+ }
656
+
657
+ if isinstance(cfg, str):
658
+ self.cfg = toml.load(cfg) if cfg.endswith(".toml") else json.load(cfg)
659
+ else:
660
+ self.cfg = cfg
661
+ self.version = self.cfg["general"]["version"]
662
+ outline_extent = self.cfg["general"]["outline_extent"]
663
+ pitch = self.cfg["general"]["pitch"]
664
+
665
+ board = Board(
666
+ stackup=self.cfg["stackup"],
667
+ padstack_defs=self.cfg["padstack_defs"],
668
+ outline_extent=outline_extent,
669
+ pitch=pitch,
670
+ pin_map=self.cfg["pin_map"],
671
+ signals=self.cfg["signals"],
672
+ differential_signals=self.cfg["differential_signals"],
673
+ )
674
+ board.populate_config(cfg_json)
675
+
676
+ self.app = Edb(
677
+ edbpath=str((Path(self.output_dir) / self.cfg["title"]).with_suffix(".aedb")), edbversion=self.version
678
+ )
679
+ self.app.configuration.load(cfg_json, apply_file=True)
680
+ self.app.save_edb()
681
+ self.app.close_edb()