def pcell(self, layout, cell=None, params=None): if cell is None: cell = layout.create_cell(self.name) cp = self.parse_param_args(params) origin, _, _ = CellWithPosition.origin_ex_ey(self, params, multiple_of_90=True) if from_library is not None: # Topcell must be same as filename gdscell = get_lib_cell(layout, cell_name, from_library) else: # BUG loading this file twice segfaults klayout layout2 = pya.Layout() layout2.read(os.path.join(gds_dir, filename)) gdscell2 = layout2.cell(cell_name) gdscell = layout.create_cell(cell_name) gdscell.copy_tree(gdscell2) del gdscell2 del layout2 rot_DTrans = pya.DTrans.R0 angle_multiple_90 = cp.angle_ex // 90 rot_DTrans.rot = (angle_multiple_90) % 4 cell.insert( pya.DCellInstArray(gdscell.cell_index(), pya.DTrans(rot_DTrans, origin))) return cell, {}
def place_cell(parent_cell, pcell, ports_dict, placement_origin, relative_to=None, transform_into=False): """ Places an pya cell and return ports with updated positions Args: parent_cell: cell to place into pcell, ports_dict: result of KLayoutPCell.pcell call placement_origin: pya.Point object to be used as origin relative_to: port name the cell is placed so that the port is located at placement_origin transform_into: if used with relative_into, transform the cell's coordinate system so that its origin is in the given port. Returns: ports(dict): key:port.name, value: geometry.Port with positions relative to parent_cell's origin """ offset = pya.DVector(0, 0) port_offset = placement_origin if relative_to is not None: offset = ports_dict[relative_to].position port_offset = placement_origin - offset if transform_into: # print(type(pcell)) offset_transform = pya.DTrans(pya.DTrans.R0, -offset) for instance in pcell.each_inst(): instance.transform(offset_transform) pcell.transform_into(offset_transform) else: placement_origin = placement_origin - offset transformation = pya.DTrans(pya.Trans.R0, placement_origin) instance = pya.DCellInstArray(pcell.cell_index(), transformation) parent_cell.insert(instance) for port in ports_dict.values(): port.position += port_offset return ports_dict
def wrapper_pcell(self, layout, cell=None, params=None): global layer_map_dict try: layer_map_dict[layout] except KeyError: layer_map_dict[layout] = pya.LayerMap() if cell is None: # copy source code of class and all its ancestors source_code = "".join( [inspect.getsource(klass) for klass in self.__class__.__mro__ if issubclass(klass, KLayoutPCell)]) # Default params before instantiation original_default_params = {name: value["default"] for name, value in self.param_definition.items()} # Updated params after instantiation and placement # (could be bigger than the original default) if params is not None: default_params = dict(self.default_params, **params) else: default_params = self.default_params # Differential parameters (non-default) diff_params = {} for name, value in original_default_params.items(): if default_params[name] != value: diff_params[name] = default_params[name] long_hash_pcell = sha256((source_code + str(diff_params) + self.name).encode()).hexdigest() short_hash_pcell = long_hash_pcell[0:7] cache_fname = f'cache_{self.__class__.__qualname__}_{short_hash_pcell}' # if short_hash_pcell in cell_cache.keys(): # already loaded # print(f"Preloaded {self.__class__.__name__}: {diff_params}") # cached_cell, ports_bytecode, cellname = cell_cache[short_hash_pcell] # ports = pickle.loads(ports_bytecode) # # print('read:', cell_index, ports, cellname) # newcell = layout.create_cell(cellname) # newcell.copy_tree(cached_cell) # # newcell.insert(pya.DCellInstArray(cell.cell_index(), # # pya.DTrans(pya.Trans.R0, pya.DPoint(0, 0)))) # return newcell, deepcopy(ports) def read_layout(layout, gds_filename): global layer_map_dict load_options = pya.LoadLayoutOptions() load_options.text_enabled = True load_options.set_layer_map(layer_map_dict[layout], True) # store and take away the cell names of all cells read so far # (by setting the cell name to "" the cells basically become invisible for # the following read) # take out the pcells cell_list = [cell for cell in layout.each_cell()] cell_indices = {cell.name: cell.cell_index() for cell in cell_list} for i in cell_indices.values(): layout.rename_cell(i, "") lmap = layout.read(gds_filename, load_options) # in the new layout, get all cells names cell_names2 = [(cell.cell_index(), cell.name) for cell in layout.each_cell()] # make those cells point to older cells prune_cells_indices = [] for i_duplicate, name_cached_cell in cell_names2: if name_cached_cell in cell_indices.keys(): if name_cached_cell.startswith('cache_'): for parent_inst_array in layout.cell(i_duplicate).each_parent_inst(): cell_instance = parent_inst_array.child_inst() cell_instance.cell = layout.cell( cell_indices[name_cached_cell]) prune_cells_indices.append(i_duplicate) else: # print('RENAME', name_cached_cell) k = 1 while (name_cached_cell + f"_{k}") in cell_indices.keys(): k += 1 layout.rename_cell(i_duplicate, name_cached_cell + f"_{k}") for i_pruned in prune_cells_indices: # print('deleting cell', layout.cell(i_pruned).name) layout.prune_cell(i_pruned, -1) # every conflict should have been caught above for name, i in cell_indices.items(): layout.rename_cell(i, name) layer_map_dict[layout] = lmap return lmap cache_fname_gds = cache_fname + '.gds' cache_fname_pkl = cache_fname + '.klayout.pkl' os.makedirs(cache_dir, mode=0o775, exist_ok=True) cache_fpath_gds = os.path.join(cache_dir, cache_fname_gds) cache_fpath_pkl = os.path.join(cache_dir, cache_fname_pkl) if os.path.isfile(cache_fpath_gds) and os.path.isfile(cache_fpath_pkl): with open(cache_fpath_pkl, 'rb') as file: ports, read_short_hash_pcell, cellname = pickle.load(file) if debug: print(f"Reading from cache: {cache_fname}: {diff_params}, {cellname}") else: print('r', end='', flush=True) if not layout.has_cell(cache_fname): read_layout(layout, cache_fpath_gds) retrieved_cell = layout.cell(cache_fname) cell = layout.create_cell(cellname) cell.insert(pya.DCellInstArray(retrieved_cell.cell_index(), pya.DTrans(pya.Trans.R0, pya.DPoint(0, 0)))) # cell.move_tree(retrieved_cell) else: if layout.has_cell(cache_fname): print(f"WARNING: {cache_fname_gds} does not exist but {cache_fname} is in layout.") # populating .gds and .pkl empty_layout = pya.Layout() compiled_cell, ports = pcell( self, empty_layout, cell=None, params=params) if debug: print(f"Writing to cache: {cache_fname}: {diff_params}, {compiled_cell.name}") else: print('w', end='', flush=True) cellname, compiled_cell.name = compiled_cell.name, cache_fname compiled_cell.write(cache_fpath_gds) with open(cache_fpath_pkl, 'wb') as file: pickle.dump((ports, short_hash_pcell, cellname), file) read_layout(layout, cache_fpath_gds) retrieved_cell = layout.cell(cache_fname) cell = layout.create_cell(cellname) cell.insert(pya.DCellInstArray(retrieved_cell.cell_index(), pya.DTrans(pya.Trans.R0, pya.DPoint(0, 0)))) else: cell, ports = pcell(self, layout, cell=cell, params=params) return cell, ports
CARAVEL_GDS_PATH = "./gds/caravel.gds" USER_GDS_PATH = "./gds/user_project_wrapper.gds" # ly = pya.CellView.active().cell.layout ly = pya.Layout() ly.read(CARAVEL_GDS_PATH) TOP = ly.cell("caravel") # x, y = 326.38500, 1382.01000 for each in TOP.each_inst(): if "user_project_wrapper" in (each.cell.name): x, y = each.dtrans.disp.x, each.dtrans.disp.y print("Placing module at (%f,%f)" % (x, y)) ly.delete_cell(ly.cell_by_name("user_project_wrapper")) ly.read(USER_GDS_PATH) tiles = ly.cell('user_project_wrapper') TOP.insert( pya.DCellInstArray(tiles.cell_index(), pya.DTrans(pya.DTrans.R0, pya.DPoint(x, y)))) for c in ly.top_cells(): if c.name != "caravel": print("removing cell " + c.name) ly.delete_cell(c.cell_index()) ly.write("./gds/caravel_merged.gds")
horizontal = np.concatenate(( np.array([0, 4, 4]), np.full(6, 4))) # Pad to total length 9 with 4's vertical = np.concatenate(( np.array([16, 20, 20]), np.full(6, 20))) # Pad to total length 9 with 20's # Generate the spiral and instantiate its cell paths.delay_spiral_geo(layout, rib, sp, turns=4, spacing=2.5, vertical=vertical, horizontal=horizontal, horizontal_mode='symmetric', vertical_mode='symmetric', quad_shift=0, start_turn=1, start_angle=0, end_angle=0, radial_shift=4.80, n_pts=5000) top.insert(pya.DCellInstArray( sp.cell_index(), pya.DTrans(pya.DVector(0, 30)) )) layout.write(my_constants.gds_models_path + 'test_spiral.gds')
def produce_impl(self): #Calculate layout database unit #dbu = self.layout.dbu dbu = 1 size = [] if len(self.size) < 2: if self.debug: print("Size < 2 dimension") if len(self.size) == 0: if self.debug: print( "paramter size has been adjusted to default - invalid data have provided" ) size = [100.0, 100.0] else: if self.debug: print("Size has been adjusted to {}:{}".format( self.size[0] / dbu, self.size[0] / dbu)) size.append(float(self.size[0]) / dbu) size.append(float(self.size[0]) / dbu) else: size.append(float(self.size[0]) / dbu) size.append(float(self.size[1]) / dbu) ovSize = [] if len(self.ovsize) < 2: if self.debug: print("overal size < 2 dimension") if len(self.ovsize) == 0: if self.debug: print( "paramter size has been adjusted to default - invalid data have provided" ) ovSize = [100.0, 100.0] else: ovSize.append(float(self.ovsize[0]) / dbu) ovSize.append(float(self.ovsize[0]) / dbu) else: ovSize.append(float(self.ovsize[0]) / dbu) ovSize.append(float(self.ovsize[1]) / dbu) armLenght = self.armLenght / dbu armWidth = self.armWidth / dbu activeArea = [size[0] - self.actOffset, size[1] - self.actOffset] woW = self.woW / dbu woOP = self.woOP / dbu # Membrane Geometry: ## arm location on a rectangle = edgeArmOffset edgeArmOffset = armWidth / 2 * math.sqrt(2) if self.debug: print("Size 0:{:.3f}, {}, {}".format(size[0], armLenght, armWidth)) ## arm starts at following points pointArmA = pya.DPoint(size[0] / 2 - edgeArmOffset, size[1] / 2) pointArmD = pya.DPoint(size[0] / 2, size[1] / 2 - edgeArmOffset) ## arm ends in the point P - might be usefull as a connector point pointP = pya.DPoint(size[0] / 2 + armLenght / math.sqrt(2), size[1] / 2 + armLenght / math.sqrt(2)) ## arm edge points offsets from the center point P armEndPointoffset = armWidth / 2 / math.sqrt(2) ## Arm edge points in relation to the P point pointArmB = pya.DPoint(pointP.x - armEndPointoffset, pointP.y + armEndPointoffset) pointArmC = pya.DPoint(pointP.x + armEndPointoffset, pointP.y - armEndPointoffset) ## Lets Try to assemble the membrane as 1/4 polyPoints = [] polyPoints.append(pya.DPoint(0.0, 0.0)) polyPoints.append(pya.DPoint(0.0, size[1] / 2)) polyPoints.append(pointArmA) polyPoints.append(pointArmB) polyPoints.append(pointArmC) polyPoints.append(pointArmD) polyPoints.append(pya.DPoint(size[0] / 2, 0.0)) #Lets put it there shapeSet = [] Poly = pya.DPolygon(polyPoints) shapeSet.append(Poly) t = pya.DCplxTrans(1.0, 180, False, 0.0, 0.0) Poly1 = pya.DPolygon(polyPoints) Poly1.transform(t) shapeSet.append(Poly1) t = pya.DCplxTrans(1.0, 0, True, 0.0, 0.0) Poly2 = pya.DPolygon(polyPoints) Poly2.transform(t) shapeSet.append(Poly2) t = pya.DCplxTrans(1.0, 180, True, 0.0, 0.0) Poly3 = pya.DPolygon(polyPoints) Poly3.transform(t) shapeSet.append(Poly3) tr = pya.DCplxTrans(1000.0) region = pya.Region(shapeSet) region.merge() region.transform(tr) self.cell.shapes(self.l_layer).insert(region) #Active Area if self.showAct: actBox = pya.DBox(-activeArea[0] / 2, -activeArea[1] / 2, activeArea[0] / 2, activeArea[1] / 2) self.cell.shapes(self.la_layer).insert(actBox) # Etch area - a rectangele limited by the membrane shape and P point etchBox = pya.DBox(-pointP.x, -pointP.y, pointP.x, pointP.y) etchRegion = pya.Region(etchBox) etchRegion.transform(tr) tempRegion = region ^ etchRegion etchRegion = tempRegion & etchRegion self.cell.shapes(self.ool_layer).insert(etchRegion) # Heater wire if self.genHeater: if self.heatType == 0: #Hilbert is defined only for square areas. We would fit whatever is smaller if activeArea[0] != activeArea[1]: if (activeArea[0] > activeArea[1]): wireArea = activeArea[1] / 2 else: wireArea = activeArea[0] / 2 else: wireArea = activeArea[0] / 2 #issue num2: # the diagonal contact placemnet is required # so we have to calculate space for the return path # segment separation 1sqg => seg = wireArea / 2^n + 1 Hcnt = 2**self.heatOrder + 1 Hseg = wireArea / (Hcnt) print("Hseq: {:.3f}".format(Hseg)) wireAreaRed = wireArea - Hseg a = wireAreaRed + wireAreaRed * 1j b = wireAreaRed - wireAreaRed * 1j z = 0 for i in range(1, self.heatOrder + 1): w = 1j * z.conjugate() z = numpy.array([w - a, z - b, z + a, b - w]) / 2 z = z.flatten() X = [x.real for x in z] Y = [x.imag for x in z] heatPoints = [] for i in range(0, len(X)): heatPoints.append(pya.DPoint(X[i], Y[i])) #lets add the return path # start with calculation of intersection to the beam # linEqa = -1*(pointP.y / pointP.x) - valid only for Square # #print("Linear equation is y = {:.3f}.x".format(linEqa)) heatInitial = heatPoints[0] pointS1 = pya.DPoint(-size[0] / 2, size[1] / 2) #pointS2 = pya.DPoint(activeArea[0]/2, -activeArea[1]/2) if self.debug: print("P:{:.3f},{:.3f} ; S:{:.3f},{:.3f}".format( -pointP.x, pointP.y, pointS1.x, pointS1.y)) linEqa = (pointP.y - pointS1.y) / (-pointP.x - pointS1.x) linEqb = pointP.y - linEqa * -pointP.x if self.debug: print("Line equation is: y={:.3f}x+{:.3f}".format( linEqa, linEqb)) heatPoints.insert( 0, pya.DPoint(heatPoints[0].x - 2 * Hseg, heatPoints[0].y)) heatPoints.insert( 0, pya.DPoint(heatPoints[0].x, linEqa * (heatPoints[0].x + Hseg) + linEqb)) heatPoints.append(pya.DPoint(heatPoints[len(heatPoints)-1].x, \ linEqa*(heatPoints[len(heatPoints)-1].x+Hseg)-linEqb)) heatPoints.append(pya.DPoint(pointP.x - Hseg, -pointP.y)) #arm contacts heatPoints.insert(0, pya.DPoint(-pointP.x - Hseg, pointP.y)) #probably somewhere here is a good time to calculate perforations # teoretically first opening should be -Heg/2 to the left of the very first # point and should repeat in X and Y axis with interval of Hseg # # center is HeatPoints[2] -Hseg/2 ? if self.perfAct: perfW = self.perfSize / 2 / dbu #perfCenter = pya.DPoint(heatPoints[2].x - Hseg, heatPoints[2].y - Hseg) #perfBox = pya.DBox(perfCenter.x-perfW, perfCenter.y-perfW, perfCenter.x+perfW, perfCenter.y-perfW) elCell = self.layout.create_cell("Perforator") perfBox = pya.DPolygon( pya.DBox(-perfW, -perfW, perfW, perfW)) if self.roundPath: perfBox = perfBox.round_corners(Hseg / 2, Hseg / 2, 32) elCell.shapes(self.perfl_layer).insert(perfBox) #lets make an array of them x_vect = pya.DVector(2 * Hseg, 0.0) y_vect = pya.DVector(0.0, 2 * Hseg) t = pya.DCplxTrans(heatInitial.x, heatInitial.y + Hseg) perfArr = pya.DCellInstArray(elCell.cell_index(), t, x_vect, y_vect, Hcnt - 1, Hcnt - 2) self.cell.insert(perfArr) #move to the right coordinates pathT = pya.DCplxTrans(Hseg, 0) heatPath = pya.DPath(heatPoints, self.heatW) heatPathT = heatPath.transformed(pathT) if self.roundPath: heatPathT = heatPath.round_corners(Hseg / 2, 32, 0.001) heatCenter = heatPathT.bbox().center() print(heatCenter) print("Rounded Path center: {}:{}".format( heatCenter.x, heatCenter.y)) pathTr = pya.DCplxTrans(-heatCenter.x, -heatCenter.y) heatPathT = heatPathT.transformed(pathTr) self.cell.shapes(self.heatl_layer).insert(heatPathT) else: print("Wire definition has not been found!") #TODO ... other types of heaters if self.genWO: #we would make a wire connection from the P point to the edge of the membrane # overpass on both sides as an option # it has to be realized as a set of the 4 path print("Overal size: {}:{}".format(ovSize[0], ovSize[1])) woPathA = pya.DPath( [pointP, pya.DPoint(ovSize[0] / 2, ovSize[1] / 2)], woW, woOP, woOP) woPathB = pya.DPath([pya.DPoint(-pointP.x, pointP.y), pya.DPoint(-ovSize[0]/2, ovSize[1]/2)],\ woW, woOP, woOP) woPathC = pya.DPath([pya.DPoint(-pointP.x, -pointP.y), pya.DPoint(-ovSize[0]/2, -ovSize[1]/2)],\ woW, woOP, woOP) woPathD = pya.DPath([pya.DPoint(pointP.x, -pointP.y), pya.DPoint(ovSize[0]/2, -ovSize[1]/2)],\ woW, woOP, woOP) self.cell.shapes(self.cntl_layer).insert(woPathA) self.cell.shapes(self.cntl_layer).insert(woPathB) self.cell.shapes(self.cntl_layer).insert(woPathC) self.cell.shapes(self.cntl_layer).insert(woPathD) if self.genCnt: # Ok that would be fun ... # so at first we should be able to find how many of the IGC we would be able to fit # in between of the perforations (maybe we should count also for the minimal separation) # principally: # single IGS pair consists of 2 wires and 2 gaps = IGSpairW? # testing condition is therefore IGSCnt = floor((Hseg - perfW) / IGSpairW) cntW = self.cntW / dbu cntSp = self.cntSp / dbu cntB = self.cntB / dbu cntBunchW = 2 * (cntW + cntSp) cntCnt = math.floor((2 * Hseg - 2 * perfW) / cntBunchW) if self.debug: print("IDC W={}".format(cntBunchW)) print("IDCs per bunch: {}".format(cntCnt)) if cntCnt == 0: print( "Error: Interdigital contacts with given specs could not be realized because of geometric containts!" ) else: #lets make a subcell with interdigital pair # so first calculate the active area - contact bars to get the lenght # contacts singles cntCell = self.layout.create_cell("IDC_subcell") cntArrCell = self.layout.create_cell("IDC_cell") #cntLenght = activeArea - 2*cntB - cntSp cntPath_p1 = pya.DPoint((cntSp + cntW) / 2, activeArea[1] / 2 - cntB) cntPath_p2 = pya.DPoint((cntSp + cntW) / 2, -activeArea[1] / 2 + cntSp + cntB) #TODO tohle je asi blbe ... cntPath_pA = [cntPath_p1, cntPath_p2] cntPath_pB = [cntPath_p1 * -1, cntPath_p2 * -1] cntPath_A = pya.DPath(cntPath_pA, cntW, 0.0, 0.0) cntPath_B = pya.DPath(cntPath_pB, cntW, 0.0, 0.0) cntCell.shapes(self.idcl_layer).insert(cntPath_A) cntCell.shapes(self.idcl_layer).insert(cntPath_B) #now lets make bunches of cntCnt and center them # TODO: tady jsem skoncil ... potreba projit odstavec pod #BEGIN x_vect = pya.DVector(cntBunchW, 0.0) y_vect = pya.DVector(0.0, 0.0) if self.debug: print("IDC bunch Vectors: {}, {}, {}, {}".format(\ x_vect.x, x_vect.y, y_vect.x, y_vect.y)) t = pya.DCplxTrans(0, 0) cntArr = pya.DCellInstArray(cntCell.cell_index(), t, x_vect, y_vect, cntCnt, 1) #center the origins on top of each other # here we have a bunch of IDCs cntArr_center = cntArr.bbox(self.layout).center() if self.debug: print("Bunch center: {},{}".format(cntArr_center.x, cntArr_center.y)) t = pya.DCplxTrans(1.0, 0, False, -cntArr_center.x, -cntArr_center.y) cntArr.transform(t) cntArrCell.insert(cntArr) # move the array to the position of Hilb. initial and paste it into the overal array a_vect = pya.DVector(2 * Hseg, 0.0) b_vect = pya.DVector(0.0, 0.0) cntLoct = pya.DCplxTrans(1.0, 0, False, heatInitial.x - Hseg, 0.0) cntArrAll = pya.DCellInstArray(cntArrCell.cell_index(), cntLoct, a_vect, b_vect, Hcnt, 1) self.cell.insert(cntArrAll) #Top and bottom contact # by principle the bar-contact should be horizontally oriented across the active zone # then they should continue to the respective P-points (upright, lowerleft) # Contact bar would be a box from the edge to the edge of active area with a width of # cntB # pointCNT1A = pya.DPoint(activeArea[0]/2, activeArea[1]/2) # if self.debug: # print("P:{:.3f},{:.3f} ; CNT:{:.3f},{:.3f}".format(-pointP.x, pointP.y, pointCNT1A.x, pointCNT1A.y)) # linCntEqa = (pointP.y-pointCNT1A.y)/(-pointP.x-pointCNT1A.x) # linCntEqb = pointP.y - linCntEqa*-pointP.x # if self.debug: # print("CNT line equation is: y={:.3f}x+{:.3f}".format(linEqa,linEqb)) # pointCNT1B = # Contact Bars cntBarW = self.cntB / dbu cntWoW = self.cntWO / dbu shapeSetCNT = [] #cntBarA shapeSetCNT.append(pya.DBox(-activeArea[0]/2, activeArea[1]/2-cntBarW,\ activeArea[0]/2, activeArea[1]/2)) #cntBarB shapeSetCNT.append(pya.DBox(-activeArea[0]/2, -activeArea[1]/2,\ activeArea[0]/2, -activeArea[1]/2+cntBarW)) pointS2 = pya.DPoint(activeArea[0] / 2, activeArea[1] / 2) #cntWOPathA shapeSetCNT.append( pya.DPath([pointS2, pointP], cntWoW, cntWoW / 2, cntWoW).polygon()) #cntWOPathB shapeSetCNT.append( pya.DPath([-pointS2, -pointP], cntWoW, cntWoW / 2, cntWoW).polygon()) for shape in shapeSetCNT: self.cell.shapes(self.idcl_layer).insert(shape) #Vias #TODO: repair position of the vias cntViaW = cntWoW * 0.9 / 2 # 10% smaller then the wire tr = pya.DCplxTrans(1.0, 45.0, False, pya.DVector(pointP)) cntViaA = pya.DPolygon(pya.DBox(-cntViaW, -cntViaW,\ cntViaW, cntViaW)).transform(tr) tr = pya.DCplxTrans(1.0, 45.0, False, pya.DVector(-pointP)) cntViaB = pya.DPolygon(pya.DBox(-cntViaW, -cntViaW,\ cntViaW, cntViaW)).transformed(tr) self.cell.shapes(self.lvia_layer).insert(cntViaA) self.cell.shapes(self.lvia_layer).insert(cntViaB)