diff --git a/examples/legacy_standalone/10_GDS_workflow.py b/examples/legacy_standalone/10_GDS_workflow.py index 13525ee49b..5ba2c5f900 100644 --- a/examples/legacy_standalone/10_GDS_workflow.py +++ b/examples/legacy_standalone/10_GDS_workflow.py @@ -54,7 +54,7 @@ # # This code sets up a simulation with HFSS and adds a frequency sweep. -setup = c.setups.add_setup("Setup1", "1GHz") +setup = c.setups.add_setup("Setup1", "1GHz", 0.02, 10) setup.add_sweep("Sweep1", "0.01GHz", "5GHz", "0.1GHz") # ## Provide additional stackup settings diff --git a/src/pyedb/dotnet/edb.py b/src/pyedb/dotnet/edb.py index eb7b7ac4f4..53277d6a63 100644 --- a/src/pyedb/dotnet/edb.py +++ b/src/pyedb/dotnet/edb.py @@ -603,7 +603,7 @@ def create_edb(self): def import_layout_pcb( self, input_file, - working_dir, + working_dir="", anstranslator_full_path="", use_ppe=False, control_file=None, @@ -616,7 +616,7 @@ def import_layout_pcb( ---------- input_file : str Full path to the board file. - working_dir : str + working_dir : str, optional Directory in which to create the ``aedb`` folder. The name given to the AEDB file is the same as the name of the board file. anstranslator_full_path : str, optional @@ -1510,6 +1510,13 @@ def import_gds_file( else: return False else: + if anstranslator_full_path and os.path.exists(anstranslator_full_path): + path = anstranslator_full_path + else: + path = os.path.join(self.base_path, "anstranslator") + if is_windows: + path += ".exe" + temp_map_file = os.path.splitext(inputGDS)[0] + ".map" temp_layermap_file = os.path.splitext(inputGDS)[0] + ".layermap" @@ -1529,10 +1536,10 @@ def import_gds_file( else: self.logger.error("Unable to define control file.") - command = [anstranslator_full_path, inputGDS, f'-g="{map_file}"', f'-c="{control_file}"'] + command = [path, inputGDS, f'-g="{map_file}"', f'-c="{control_file}"'] else: command = [ - anstranslator_full_path, + path, inputGDS, f'-o="{control_file_temp}"' f'-t="{tech_file}"', f'-g="{map_file}"', diff --git a/src/pyedb/dotnet/edb_core/edb_data/control_file.py b/src/pyedb/dotnet/edb_core/edb_data/control_file.py index 076b85f575..3d3e191aec 100644 --- a/src/pyedb/dotnet/edb_core/edb_data/control_file.py +++ b/src/pyedb/dotnet/edb_core/edb_data/control_file.py @@ -25,6 +25,7 @@ import re import subprocess import sys +import xml from pyedb.edb_logger import pyedb_logger from pyedb.generic.general_methods import ET, env_path, env_value, is_linux @@ -964,14 +965,14 @@ def _write_xml(self, root): class ControlFileSetup: """Setup Class.""" - def __init__(self, name): + def __init__(self, name, adapt_freq="1GHz", maxdelta=0.02, maxpasses=10): self.name = name self.enabled = True self.save_fields = False self.save_rad_fields = False - self.frequency = "1GHz" - self.maxpasses = 10 - self.max_delta = 0.02 + self.frequency = adapt_freq + self.maxpasses = maxpasses + self.max_delta = maxdelta self.union_polygons = True self.small_voids_area = 0 self.mode_type = "IC" @@ -1082,22 +1083,25 @@ class ControlFileSetups: def __init__(self): self.setups = [] - def add_setup(self, name, frequency): + def add_setup(self, name, adapt_freq, maxdelta, maxpasses): """Add a new setup Parameters ---------- name : str - Setup name. - frequency : str + Setup Name. + adapt_freq : str, optional Setup Frequency. + maxdelta : float, optional + Maximum Delta. + maxpasses : int, optional + Maximum Number of Passes. Returns ------- :class:`pyedb.dotnet.edb_core.edb_data.control_file.ControlFileSetup` """ - setup = ControlFileSetup(name) - setup.frequency = frequency + setup = ControlFileSetup(name, adapt_freq, maxdelta, maxpasses) self.setups.append(setup) return setup @@ -1112,17 +1116,17 @@ class ControlFile: def __init__(self, xml_input=None, tecnhology=None, layer_map=None): self.stackup = ControlFileStackup() + self.boundaries = ControlFileBoundaries() + self.setups = ControlFileSetups() if xml_input: self.parse_xml(xml_input) if tecnhology: self.parse_technology(tecnhology) if layer_map: self.parse_layer_map(layer_map) - self.boundaries = ControlFileBoundaries() self.remove_holes = False self.remove_holes_area_minimum = 30 self.remove_holes_units = "um" - self.setups = ControlFileSetups() self.components = ControlFileComponents() self.import_options = ControlFileImportOptions() pass @@ -1262,6 +1266,50 @@ def parse_xml(self, xml_input): via.remove_unconnected = ( True if i.attrib["RemoveUnconnected"] == "true" else False ) + if el.tag == "Boundaries": + for port_el in el: + if port_el.tag == "CircuitPortPt": + self.boundaries.add_port( + name=port_el.attrib["Name"], + x1=port_el.attrib["x1"], + y1=port_el.attrib["y1"], + layer1=port_el.attrib["Layer1"], + x2=port_el.attrib["x2"], + y2=port_el.attrib["y2"], + layer2=port_el.attrib["Layer2"], + z0=port_el.attrib["Z0"], + ) + setups = root.find("SimulationSetups") + if setups: + hfsssetup = setups.find("HFSSSetup") + if hfsssetup: + if "Name" in hfsssetup.attrib: + name = hfsssetup.attrib["Name"] + hfsssimset = hfsssetup.find("HFSSSimulationSettings") + if hfsssimset: + hfssadaptset = hfsssimset.find("HFSSAdaptiveSettings") + if hfssadaptset: + adaptset = hfssadaptset.find("AdaptiveSettings") + if adaptset: + singlefreqdatalist = adaptset.find("SingleFrequencyDataList") + if singlefreqdatalist: + adaptfreqdata = singlefreqdatalist.find("AdaptiveFrequencyData") + if adaptfreqdata: + if isinstance( + adaptfreqdata.find("AdaptiveFrequency"), xml.etree.ElementTree.Element + ): + adapt_freq = adaptfreqdata.find("AdaptiveFrequency").text + else: + adapt_freq = "1GHz" + if isinstance(adaptfreqdata.find("MaxDelta"), xml.etree.ElementTree.Element): + maxdelta = adaptfreqdata.find("MaxDelta").text + else: + maxdelta = 0.02 + if isinstance(adaptfreqdata.find("MaxPasses"), xml.etree.ElementTree.Element): + maxpasses = adaptfreqdata.find("MaxPasses").text + else: + maxpasses = 10 + self.setups.add_setup(name, adapt_freq, maxdelta, maxpasses) return True def write_xml(self, xml_output): @@ -1278,8 +1326,7 @@ def write_xml(self, xml_output): """ control = ET.Element("{http://www.ansys.com/control}Control", attrib={"schemaVersion": "1.0"}) self.stackup._write_xml(control) - if self.boundaries.ports or self.boundaries.extents: - self.boundaries._write_xml(control) + self.boundaries._write_xml(control) if self.remove_holes: hole = ET.SubElement(control, "RemoveHoles") hole.set("HoleAreaMinimum", str(self.remove_holes_area_minimum)) diff --git a/tests/example_models/cad/GDS/sky130_fictitious_dtc_example_control_no_map.xml b/tests/example_models/cad/GDS/sky130_fictitious_dtc_example_control_no_map.xml index a27a8562d5..51abc4db35 100644 --- a/tests/example_models/cad/GDS/sky130_fictitious_dtc_example_control_no_map.xml +++ b/tests/example_models/cad/GDS/sky130_fictitious_dtc_example_control_no_map.xml @@ -106,4 +106,25 @@ + + + + + + + + + + + + 0.5GHz + 0.01 + 10 + + + + + + + \ No newline at end of file diff --git a/tests/legacy/system/test_edb.py b/tests/legacy/system/test_edb.py index 211440484d..8c604cdf7c 100644 --- a/tests/legacy/system/test_edb.py +++ b/tests/legacy/system/test_edb.py @@ -1341,7 +1341,7 @@ def test_import_gds_from_tech(self): self.local_scratch.copyfile(gds_in, gds_out) c = ControlFile(c_file_in, layer_map=c_map) - setup = c.setups.add_setup("Setup1", "1GHz") + setup = c.setups.add_setup("Setup1", "1GHz", 0.02, 10) setup.add_sweep("Sweep1", "0.01GHz", "5GHz", "0.1GHz") c.boundaries.units = "um" c.stackup.units = "um" @@ -1364,8 +1364,8 @@ def test_import_gds_from_tech(self): ) assert edb - assert "P1" in edb.excitations - assert "Setup1" in edb.setups + assert "P1" and "P2" in edb.excitations + assert "Setup1" and "Setup Test" in edb.setups assert "B1" in edb.components.instances edb.close() @@ -1495,7 +1495,7 @@ def test_add_layer_api_with_control_file(self): ) assert ctrl.boundaries.ports # setup using q3D for DC point - setup = ctrl.setups.add_setup("test_setup", "10GHz") + setup = ctrl.setups.add_setup("test_setup", "10GHz", 0.02, 10) assert setup setup.add_sweep( name="test_sweep",