def test_pad_pcell(top_cell): pad = DCPad(name="testname") pad.params.layer_metal = kdb.LayerInfo(1, 0) pad.params.layer_opening = kdb.LayerInfo(2, 0) # This will get automatically converted to LayerInfo # No Error pad.params.layer_metal = "1/0" # TODO set defaults here TOP, layout = top_cell() cell, ports = pad.new_cell(layout) assert "el0" in ports origin, angle = kdb.DPoint(0, 0), 0 TOP.insert_cell(cell, origin, angle) TOP.write("tests/tmp/pad.gds")
def find_labels(gdspath: Path, label_layer: Tuple[int, int] = LAYER.LABEL, prefix: str = "opt_") -> Iterator[Tuple[str, float, float]]: """finds labels and locations from a GDS file""" # Load the layout gdspath = str(gdspath) layout = pya.Layout() layout.read(gdspath) # Get the top cell and the units, and find out the index of the layer topcell = layout.top_cell() dbu = layout.dbu layer = pya.LayerInfo(label_layer[0], label_layer[1]) layer_index = layout.layer(layer) # Extract locations iterator = topcell.begin_shapes_rec(layer_index) while not (iterator.at_end()): shape, trans = iterator.shape(), iterator.trans() iterator.next() if shape.is_text(): text = shape.text if text.string.startswith(prefix): transformed = text.transformed(trans) yield text.string, transformed.x * dbu, transformed.y * dbu
def add_layer(self, layer_name, layer_def): """Adds a layer to the technology file. layer_name: str: name of layer. (Useless in GDS, useful in OASIS) layer_def: str: 10/0, 10 = layer index, 0, datatype """ layer_idx, datatype = layer_def.split("/") layer_idx = int(layer_idx) datatype = int(datatype) self.layers[layer_name] = kdb.LayerInfo(layer_idx, datatype, layer_name)
def __init__(self): # Important: initialize the super class super(TLM, self).__init__() # self.param("lay", self.TypeLayer, "Layer", default = pya.LayerInfo(2, 0)) self.param("ito", self.TypeLayer, "Conductor Layer", default=pya.LayerInfo(1, 0, "ITO Opening 1/0")) self.param("nno", self.TypeLayer, "Pad Layer", default=pya.LayerInfo(2, 0, "NNO Pad 2/0")) self.param("fox", self.TypeLayer, "Isolation Layer", default=pya.LayerInfo(3, 0, "Field Oxide 3/0")) self.param("hfoetch", self.TypeLayer, "Isolation Layer", default=pya.LayerInfo(4, 0, "HfO2 Etch 4/0")) # self.param("l", self.TypeList, "Length (Squares)", default= [2, 5, 10, 20]) self.param("w", self.TypeDouble, "Width (um)", default=40)
def main(): layout = pya.Layout() TOP = layout.create_cell("TOP") layer = pya.LayerInfo(1, 0) # First layer origin = pya.DPoint(0, 0) ex = pya.DVector(1, 0) ey = pya.DVector(0, 1) angles = np.linspace(-170, 170, 13) for i, angle_0 in enumerate(angles): for j, angle_3 in enumerate(angles): curve = bezier_curve(origin + ey * i * 150 + ex * j * 150, angle_0, angle_3, ex, ey) layout_waveguide(TOP, layer, curve, width=0.5) layout.write("bezier_waveguides.gds")
def find_labels(gdspath, label_layer=201, label_purpose=0): """ finds labels and locations from a GDS file """ # Load the layout gdspath = str(gdspath) layout = pya.Layout() layout.read(gdspath) # Get the top cell and the units, and find out the index of the layer topcell = layout.top_cell() dbu = layout.dbu layer = pya.LayerInfo(label_layer, label_purpose) layer_index = layout.layer(layer) # Extract locations iterator = topcell.begin_shapes_rec(layer_index) while not (iterator.at_end()): shape, trans = iterator.shape(), iterator.trans() iterator.next() if shape.is_text(): text = shape.text transformed = text.transformed(trans) yield text.string, transformed.x * dbu, transformed.y * dbu
def main(): def trace_rounded_path(cell, layer, rounded_path, width): points = [] for item in rounded_path: points.extend(item.get_points()) dpath = kdb.DPath(points, width, 0, 0) cell.shapes(layer).insert(dpath) def trace_reference_path(cell, layer, points, width): dpath = kdb.DPath(points, width, 0, 0) cell.shapes(layer).insert(dpath) layout = kdb.Layout() TOP = layout.create_cell("TOP") layer = kdb.LayerInfo(10, 0) layerRec = kdb.LayerInfo(1001, 0) ex, ey = kdb.DPoint(1, 0), kdb.DPoint(0, 1) points = [0 * ex, 10 * ex, 10 * (ex + ey), 30 * ex] origin = 0 * ey points = [origin + point for point in points] x = compute_rounded_path(points, 3) trace_rounded_path(TOP, layer, x, 0.5) trace_reference_path(TOP, layerRec, points, 0.5) points = [0 * ex, 10 * ex, 5 * (ex - ey), 17 * ex, 30 * ex] origin = 30 * ey points = [origin + point for point in points] x = compute_rounded_path(points, 3) trace_rounded_path(TOP, layer, x, 0.5) trace_reference_path(TOP, layerRec, points, 0.5) radius = 3 for ex2 in (ex, -ex): points = [2 * ex2] for d in np.arange(1, 10, 2.5): origin = points[-1] displacements = [ 4 * radius * ex2, 4 * radius * ex2 + d * ey - 1 * d * ex2, d * ey, (d + 2 * radius) * ey, ] points += [origin + displacement for displacement in displacements] origin = 15 * ex + 40 * ey points = [origin + point for point in points] x = compute_rounded_path(points, radius) trace_rounded_path(TOP, layer, x, 0.5) trace_reference_path(TOP, layerRec, points, 0.5) # Layout tapered waveguide points = [ 0 * ex, 100 * ex, 100 * ex + 20 * ey, 10 * ex + 5 * ey, 10 * ex + 25 * ey, 100 * ex + 30 * ey, ] # Untapered origin = 40 * ex points_ = [origin + point for point in points] layout_waveguide_from_points(TOP, layer, points_, 0.5, 5) # Tapered origin = 40 * ex + 40 * ey points_ = [origin + point for point in points] layout_waveguide_from_points( TOP, layer, points_, 0.5, 5, taper_width=3, taper_length=10 ) print("Wrote waveguide_rounding.gds") TOP.write("waveguide_rounding.gds")
def place_from_yaml( filepath_yaml: Path, root_does: Path = CONFIG["cache_doe_directory"], precision: float = 1e-9, fontpath: Path = text.FONT_PATH, default_align_x: NSEW = "W", default_align_y: NSEW = "S", default_margin: int = 10, default_x0: NSEW = "E", default_y0: NSEW = "S", ) -> Cell: """Returns a gds cell composed of DOEs/components given in a yaml file allows for each DOE to have its own x and y spacing (more flexible than method1) Args: filepath_yaml: root_does: used for cache, requires content.txt """ transform_identity = pya.Trans(0, 0) dicts, mask_settings = load_yaml(filepath_yaml) does, templates = separate_does_from_templates(dicts) placed_doe = None placed_does = {} top_level_name = mask_settings.get("name", "TOP_LEVEL") layer_doe_label = mask_settings["layer_doe_label"] top_level_layout = pya.Layout() # Set database units according to precision top_level_layout.dbu = precision / 1e-6 dbu = top_level_layout.dbu um_to_grid = int(1 / dbu) top_level = top_level_layout.create_cell(top_level_name) global CELLS CELLS[top_level_name] = top_level_layout default_doe_settings = { "add_doe_label": False, "add_doe_visual_label": False, "dx_visual_label": 0, "dy_visual_label": 0, } for doe_name, doe in does.items(): # If a template is specified, apply it if "template" in doe: doe_templates = doe["template"] if type(doe_templates) != list: doe_templates = [doe_templates] for doe_template in doe_templates: try: doe = update_dicts_recurse(doe, templates[doe_template]) except BaseException: print(doe_template, "does not exist") raise doe = update_dicts_recurse(doe, default_doe_settings) # Get all the components components = load_doe(doe_name, root_does) # Check that the high level components are all unique # For now this is mostly to circumvent a bug # But the design manual also specifies that DOE components should have # unique names. So one instance per cell if components: if len(components) != len( set([_c.top_cell().name for _c in components])): __dict_component_debug = {} for _c in components: _name = _c.top_cell().name if _name not in __dict_component_debug: __dict_component_debug[_name] = 0 __dict_component_debug[_name] += 1 duplicates_components = [ _name for _name, _count in __dict_component_debug.items() if _count > 1 ] print( "Please remove duplicate components at DOE entry level: ") print(duplicates_components) components = [ import_cell(top_level_layout, _c.top_cell()) for _c in components ] default_placer_settings = { "align_x": default_align_x, "align_y": default_align_y, "margin": default_margin, "x0": default_x0, "y0": default_y0, } settings = default_placer_settings.copy() placer = doe.get("placer") if placer: placer_type = placer.pop("type", "pack_col") settings.update(doe["placer"]) else: placer_type = "pack_col" if placer_type not in PLACER_NAME2FUNC: raise ValueError( f"{placer_type} is not an available placer, Choose:" f" {list(PLACER_NAME2FUNC.keys())}") _placer = PLACER_NAME2FUNC[placer_type] # All other attributes are assumed to be settings for the placer # Check if the cell should be attached to a specific parent cell if "parent" in settings: parent_name = settings.pop("parent") if parent_name not in CELLS: # Create parent cell in layout and insert it under top level parent_cell = top_level_layout.create_cell(parent_name) CELLS[parent_name] = parent_cell parent_cell_instance = pya.CellInstArray( parent_cell.cell_index(), transform_identity) top_level.insert(parent_cell_instance) doe_parent_cell = CELLS[parent_name] else: # If no parent specified, insert the DOE at top level doe_parent_cell = top_level # Check if we should create a DOE cell which regroups the DOEs if "with_doe_cell" in settings: with_doe_cell = settings.pop("with_doe_cell") else: with_doe_cell = True # x0, y0 can either be float or string x0 = settings.pop("x0") y0 = settings.pop("y0") # Check whether we are doing relative or absolute placement # if (x0 in ["E", "W"] or y0 in ["N", "S"]) and not placed_doe: # raise ValueError( # "At least one DOE must be placed to use relative placement" # ) # For relative placement (to previous DOE) if "margin_x" not in settings: settings["margin_x"] = settings["margin"] if "margin_y" not in settings: settings["margin_y"] = settings["margin"] if "inter_margin_x" not in settings: inter_margin_x = settings["margin_x"] else: inter_margin_x = settings.pop("inter_margin_x") if "inter_margin_y" not in settings: inter_margin_y = settings["margin_y"] else: inter_margin_y = settings.pop("inter_margin_y") align_x = settings["align_x"] align_y = settings["align_y"] # Make sure that the alignment is sensible depending on how we stack # If we specify a DOE to place next to, use it if "next_to" in settings: placed_doe = placed_does[settings.pop("next_to")] # print(placed_doe) # print(placed_does) # Otherwise, use previously placed DOE as starting point doe_si = (SizeInfo(placed_doe, top_level_layout, um_to_grid=um_to_grid) if placed_doe is not None else None) if x0 == "E": x0 = doe_si.east if align_x == "W": x0 += inter_margin_x if x0 == "W": x0 = doe_si.west if align_x == "E": x0 -= inter_margin_x if y0 == "N": y0 = doe_si.north if align_y == "S": y0 += inter_margin_y if y0 == "S": y0 = doe_si.south if align_y == "N": y0 -= inter_margin_y # Add x0, y0 in settings as float settings["x0"] = x0 settings["y0"] = y0 settings["um_to_grid"] = um_to_grid placed_components = _placer(components, **settings) # Place components within a cell having the DOE name if with_doe_cell or len(placed_components) > 1: doe_cell = top_level_layout.create_cell(doe_name) CELLS[doe_name] = doe_cell for instance in placed_components: doe_cell.insert(instance) placed_does[doe_name] = doe_cell placed_doe = doe_cell doe_instance = pya.CellInstArray(doe_cell.cell_index(), transform_identity) else: # If only single cell and we want to skip the sweep cell doe_instance = placed_components[0] placed_does[doe_name] = doe_instance placed_doe = doe_instance add_doe_label = doe["add_doe_label"] add_doe_visual_label = doe["add_doe_visual_label"] if add_doe_label: layer_label_index, layer_label_datatype = layer_doe_label layer_index = top_level.layout().insert_layer( pya.LayerInfo(layer_label_index, layer_label_datatype)) # Add the name of the DOE at the center of the cell _p = doe_instance.bbox(top_level_layout).center() _text = pya.Text(doe_name, _p.x, _p.y) top_level.shapes(layer_index).insert(_text) if add_doe_visual_label: _bbox = doe_instance.bbox(top_level_layout) idbu = 1 / top_level.layout().dbu x_text = _bbox.center().x + doe["dx_visual_label"] * idbu y_text = _bbox.bottom + (15.0 + doe["dy_visual_label"]) * idbu _text = text.add_text(top_level, doe_name, position=(x_text, y_text), fontpath=fontpath) # _transform = pya.DTrans(x_text, y_text) # top_level.insert(pya.CellInstArray(_text.cell_index(), _transform)) doe_parent_cell.insert(doe_instance) return top_level
def test_layers(): t = ExampleTech() assert t.layers["layer_metal"] == kdb.LayerInfo(1, 0, "layer_metal")
import os import klayout.db as pya path = os.path.dirname(os.path.abspath(__file__)) print(path) layout = pya.Layout() # create layers to use in our layout. LayerInfo takes layer#, datatype, text label as inputs ito = pya.LayerInfo(1, 0, "ITO Opening 1/0") nno = pya.LayerInfo(2, 0, "NNO Pad 2/0") fox = pya.LayerInfo(3, 0, "Field Oxide 3/0") hfoetch = pya.LayerInfo(4, 0, "HfO2 Etch 4/0") # assign layers to layout for l in [ito, nno, fox, hfoetch]: layout.layer(l) top = layout.create_cell("TOP") top.shapes(ito.layer).insert(pya.Box([0, 0], [1000,1000])) layout.write(os.path.join(path, 'test.gds'))
def test_load_from_xml(): filepath = Path(os.path.dirname(__file__)).resolve() / "EBeam.lyp" ebeam = Tech.load_from_xml(filepath) assert ebeam.layers["M1"] == kdb.LayerInfo(41, 0, "M1")