# This code is part of KQCircuits
# Copyright (C) 2021 IQM Finland Oy
#
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with this program. If not, see
# https://www.gnu.org/licenses/gpl-3.0.html.
#
# The software distribution should follow IQM trademark policy for open-source software
# (meetiqm.com/iqm-open-source-trademark-policy). IQM welcomes contributions to the code.
# Please see our contribution agreements for individuals (meetiqm.com/iqm-individual-contributor-license-agreement)
# and organizations (meetiqm.com/iqm-organization-contributor-license-agreement).
import os.path
from pathlib import Path
from kqcircuits.pya_resolver import pya
from kqcircuits.defaults import default_layers
from kqcircuits.simulations.export.sonnet import parser
from kqcircuits.simulations.export.util import find_edge_from_point_in_polygons
from kqcircuits.simulations.port import InternalPort, EdgePort
from kqcircuits.simulations.simulation import Simulation, get_simulation_layer_by_name
from kqcircuits.util.export_helper import write_commit_reference_file
[docs]
def poly_and_edge_indices(polygons, dbu, port, number, location, group):
i, j, _ = find_edge_from_point_in_polygons(polygons, location, dbu, tolerance=5.0) # hardcoded, feel free to change
return parser.port(
portnum=number,
ipolygon=i + 1,
ivertex=j,
port_type=("CUP" if group else "STD"),
group=group,
resist=port.resistance,
react=port.reactance,
induct=port.inductance,
capac=port.capacitance,
)
[docs]
def export_sonnet_son(
simulation: Simulation,
path: Path,
detailed_resonance=False,
lower_accuracy=False,
current=False,
control="ABS",
fill_type="Staircase",
simulation_safety=0,
):
"""
Export simulation into son file.
Arguments:
simulation: The simulation to be exported.
path: Location where to write son files.
detailed_resonance: More info in
www.sonnetsoftware.com/support/downloads/techdocs/Enhanced_Resonance_Detection_Feature.pdf
lower_accuracy: False sets Sonnet to Fine/Edge meshing and True to Coarse/Edge Meshing.
fill_type: Value 'Staircase' sets the default fill type for polygons. The other option is 'Conformal' which can
be faster but less accurate. A good workflow could be to set everything to Staircase and then set some
meanders to Conformal in Sonnet.
current: True computes currents in Sonnet which adds to simulation time but can used to easily see connection
errors.
control: Selects what analysis control is used in Sonnet. Options are 'Simple', 'ABS' and 'Sweep' for parameter
sweeping.
simulation_safety: Adds extra ground area to a simulation environment (in µm).
Returns:
Path to exported son file.
"""
if simulation is None or not isinstance(simulation, Simulation):
raise ValueError("Cannot export without simulation")
def get_sonnet_strings(material_type, grid_size, symmetry):
layout = simulation.cell.layout()
dbu = layout.dbu
layer_pad = layout.layer(get_simulation_layer_by_name("1t1_airbridge_pads"))
layer_bridge = layout.layer(get_simulation_layer_by_name("1t1_airbridge_flyover"))
layer_son = layout.layer(get_simulation_layer_by_name("1t1_signal"))
layer_son_ground = layout.layer(get_simulation_layer_by_name("1t1_ground"))
simpolygons = [p.polygon for p in simulation.cell.shapes(layer_son).each()] + [
p.polygon for p in simulation.cell.shapes(layer_son_ground).each()
]
airbridge_polygons = [p.polygon for p in simulation.cell.shapes(layer_bridge).each()]
airpads_polygons = [p.polygon for p in simulation.cell.shapes(layer_pad).each()]
for p in airpads_polygons:
p.isVia = True
level_iter = iter(
len(simpolygons) * [(2 if material_type == "Si+Al" else 0)]
+ len(airbridge_polygons) * [1]
+ len(airpads_polygons) * [2]
)
polys = parser.polygons(
simpolygons + airbridge_polygons + airpads_polygons,
pya.DVector(-simulation.box.p1.x, -simulation.box.p2.y),
dbu,
# get the bottom left corner
ilevel=level_iter,
fill_type=("V" if (fill_type == "Conformal") else "N"),
)
# find port edges
sstring_ports = ""
refplane_dirs = []
port_ipolys = []
group_ascii = ord("A")
calgroup = ""
if simulation.use_ports:
for port in simulation.ports:
if isinstance(port, InternalPort):
sstring_ports += poly_and_edge_indices(
simpolygons + airbridge_polygons, dbu, port, port.number, port.signal_location, chr(group_ascii)
)
sstring_ports += poly_and_edge_indices(
simpolygons + airbridge_polygons,
dbu,
port,
-port.number,
port.ground_location,
chr(group_ascii),
)
calgroup += f'CUPGRP "{chr(group_ascii)}"\nID 28\nGNDREF F\nTWTYPE FEED\nEND\n'
group_ascii += 1
elif isinstance(port, EdgePort):
# TODO: re-implement calibration
#
# refplane_dirs.append(port.side)
# ipoly = poly_and_edge_indeces(cell, simpolygons + airbridge_polygons, dbu, port, ls)
# logging.info(re.findall(r'POLY (\d+)', ipoly))
# port_ipolys.append(re.findall(r'POLY (\d+)', ipoly)) # scan ipolygon
sstring_ports += poly_and_edge_indices(
simpolygons + airbridge_polygons, dbu, port, port.number, port.signal_location, ""
)
sonnet_box = parser.box(
xwidth=simulation.box.width(),
ywidth=simulation.box.height(),
xcells=int(simulation.box.width() / grid_size),
ycells=int(simulation.box.height() / grid_size),
materials_type=material_type,
)
return {
"polygons": polys,
"box": sonnet_box,
"ports": sstring_ports,
"calgroup": calgroup,
"refplanes": parser.refplanes(refplane_dirs, simulation_safety, port_ipolys),
"symmetry": parser.symmetry(symmetry),
"nports": len({abs(port.number) for port in simulation.ports}),
"resonance_abs": "DET_ABS_RES Y" if detailed_resonance else "DET_ABS_RES N",
"lower_accuracy": "1" if lower_accuracy else "0",
"current": "j" if current else "",
}
# detect airbridges
shapes_in_air = simulation.layout.begin_shapes(
simulation.cell, simulation.layout.layer(default_layers["1t1_airbridge_flyover"])
)
materials_type = "Si+Al" if not shapes_in_air.shape().is_null() else "Si BT"
sonnet_strings = get_sonnet_strings(materials_type, 1, False)
sonnet_strings["control"] = parser.control(control)
son_filename = str(path.joinpath(simulation.name + ".son"))
parser.apply_template(
os.path.join(os.path.dirname(os.path.abspath(parser.__file__)), "template.son"), son_filename, sonnet_strings
)
return son_filename
[docs]
def export_sonnet(
simulations,
path: Path,
detailed_resonance=False,
lower_accuracy=False,
current=False,
control="ABS",
fill_type="Staircase",
simulation_safety=0,
):
"""
Export Sonnet simulations by writing son files.
Arguments:
simulations: List of simulations to be exported.
path: Location where to write export files.
detailed_resonance: More info in
www.sonnetsoftware.com/support/downloads/techdocs/Enhanced_Resonance_Detection_Feature.pdf
lower_accuracy: False sets Sonnet to Fine/Edge meshing and True to Coarse/Edge Meshing.
fill_type: Value 'Staircase' sets the default fill type for polygons. The other option is 'Conformal' which can
be faster but less accurate. A good workflow could be to set everything to Staircase and then set some
meanders to Conformal in Sonnet.
current: True computes currents in Sonnet which adds to simulation time but can used to easily see connection
errors.
control: Selects what analysis control is used in Sonnet. Options are 'Simple', 'ABS' and 'Sweep' for parameter
sweeping.
simulation_safety: Adds extra ground area to a simulation environment (in µm).
Returns:
List of paths to exported son files.
"""
write_commit_reference_file(path)
son_filenames = []
for simulation in simulations:
son_filenames.append(
export_sonnet_son(
simulation,
path,
detailed_resonance=detailed_resonance,
lower_accuracy=lower_accuracy,
current=current,
control=control,
fill_type=fill_type,
simulation_safety=simulation_safety,
)
)
return son_filenames