def labelITerm(iterm, pin_name, iotype, all_shapes_flag=False): net_name = pin_name net = chip_block.findNet(net_name) if net is None: net = odb.dbNet_create(chip_block, net_name) pin_bterm = chip_block.findBTerm(pin_name) if pin_bterm is None: pin_bterm = odb.dbBTerm_create(net, pin_name) assert pin_bterm is not None, "Failed to create or find " + pin_name pin_bterm.setIoType(iotype) pin_bpin = odb.dbBPin_create(pin_bterm) pin_bpin.setPlacementStatus("PLACED") if not all_shapes_flag: boxes = getBiggestBox(pad_iterm) else: boxes = getAllBoxes(pad_iterm) for box in boxes: layer, ll, ur = box odb.dbBox_create(pin_bpin, layer, ll.getX(), ll.getY(), ur.getX(), ur.getY()) pad_iterm.connect(net) pin_bterm.connect(net)
return box def getTechMaxSpacing(layers=tech.getLayers()): max_spacing = -1 for layer in layers: max_spacing = max(max_spacing, layer.getSpacing()) # print("Max spacing for", layers, "is", max_spacing) return max_spacing print("Top-level design name:", top_design_name) # create special nets for special_net_name in SPECIAL_NETS: net = odb.dbNet_create(block_top, special_net_name) net.setSpecial() net.setWildConnected() wire = odb.dbSWire_create(net, "ROUTED") SPECIAL_NETS[special_net_name]["net"] = net SPECIAL_NETS[special_net_name]["wire"] = wire ALL_BOXES = [] # disconnect all iterms from anywhere else !!! (is this even needed?) for inst in block_top.getInsts(): iterms = inst.getITerms() ALL_BOXES += getInstObs(inst) for iterm in iterms:
def power_route( output, input_lef, core_vdd_pin, core_gnd_pin, vdd_pad_pin_map, gnd_pad_pin_map, input_def, ): """ Takes a pre-routed top-level layout, produces a DEF file with VDD and GND special nets\ where the power pads are connected to the core ring """ # TODO: expose through arguments ORIENT_LOC_MAP = {"R0": "N", "R90": "W", "R180": "S", "R270": "E"} # TODO: allow control VDD_NET_NAME = "VDD" GND_NET_NAME = "GND" # SKY130 DEFAULT SPECIAL_NETS = { VDD_NET_NAME: { "core_pin": core_vdd_pin, "covered": False, "map": [] }, GND_NET_NAME: { "core_pin": core_gnd_pin, "covered": False, "map": [] }, } if vdd_pad_pin_map is None: vdd_pad_pin_map = [{"pad_pin": "vccd", "pad_name_substr": "vccd"}] else: vdd_pad_pin_map = [{ "pad_pin": mapping[0], "pad_name_substr": mapping[1] } for mapping in vdd_pad_pin_map] if gnd_pad_pin_map is None: gnd_pad_pin_map = [ { "pad_pin": "vssd", "pad_name_substr": "vssd" }, { "pad_pin": "vssa", "pad_name_substr": "vssa" }, { "pad_pin": "vssio", "pad_name_substr": "vssio" }, ] else: gnd_pad_pin_map = [{ "pad_pin": mapping[0], "pad_name_substr": mapping[1] } for mapping in gnd_pad_pin_map] SPECIAL_NETS[VDD_NET_NAME]["map"] = vdd_pad_pin_map SPECIAL_NETS[GND_NET_NAME]["map"] = gnd_pad_pin_map ################## reader = OdbReader(input_lef, input_def) via_rules = reader.tech.getViaGenerateRules() print("Found", len(via_rules), "VIA GENERATE rules") # build dictionary of basic custom vias (ROW = COL = 1) custom_vias = {} for rule in via_rules: lower_rules = rule.getViaLayerRule(0) upper_rules = rule.getViaLayerRule(1) cut_rules = rule.getViaLayerRule(2) lower_layer = lower_rules.getLayer() upper_layer = upper_rules.getLayer() cut_layer = cut_rules.getLayer() if lower_layer.getName() in custom_vias: print("Skipping", rule.getName()) continue via_params = odb.dbViaParams() via_params.setBottomLayer(lower_layer) via_params.setTopLayer(upper_layer) via_params.setCutLayer(cut_layer) cut_rect = cut_rules.getRect() via_params.setXCutSize(cut_rect.dx()) via_params.setYCutSize(cut_rect.dy()) cut_spacing = cut_rules.getSpacing() via_params.setXCutSpacing(cut_spacing[0] - cut_rect.dx()) via_params.setYCutSpacing(cut_spacing[1] - cut_rect.dy()) lower_enclosure = lower_rules.getEnclosure() upper_enclosure = upper_rules.getEnclosure() if "M1M2" in rule.getName(): print(lower_enclosure) via_params.setXBottomEnclosure(lower_enclosure[0]) via_params.setYBottomEnclosure(lower_enclosure[1]) via_params.setXTopEnclosure(upper_enclosure[0]) via_params.setYTopEnclosure(upper_enclosure[1]) custom_vias[lower_layer.getName()] = {} custom_vias[lower_layer.getName()][upper_layer.getName()] = { "rule": rule, "params": via_params, } print("Added", rule.getName()) def create_custom_via(layer1, layer2, width, height, reorient="R0"): assert width > 0 and height > 0 if layer1.getRoutingLevel() < layer2.getRoutingLevel(): lower_layer_name = layer1.getName() upper_layer_name = layer2.getName() elif layer1.getRoutingLevel() > layer2.getRoutingLevel(): lower_layer_name = layer2.getName() upper_layer_name = layer1.getName() else: raise Exception( "Cannot create a custom via between two identical layers") if reorient in ["R90", "R270"]: width, height = height, width via_name = "via_%s_%s_%dx%d" % ( lower_layer_name, upper_layer_name, width, height, ) custom_via = reader.block.findVia(via_name) if not custom_via: print("Creating", via_name) via_params = custom_vias[lower_layer_name][upper_layer_name][ "params"] via_rule = custom_vias[lower_layer_name][upper_layer_name]["rule"] cut_width = via_params.getXCutSize() cut_height = via_params.getYCutSize() cut_spacing_x = via_params.getXCutSpacing() cut_spacing_y = via_params.getXCutSpacing() lower_enclosure_x = via_params.getXBottomEnclosure() lower_enclosure_y = via_params.getYBottomEnclosure() upper_enclosure_x = via_params.getXTopEnclosure() upper_enclosure_y = via_params.getYTopEnclosure() custom_via = odb.dbVia_create(reader.block, via_name) custom_via.setViaGenerateRule(via_rule) array_width = width - 2 * max(lower_enclosure_x, upper_enclosure_x) array_height = height - 2 * max(lower_enclosure_y, upper_enclosure_y) # set ROWCOL rows = 1 + (array_height - cut_height) // (cut_spacing_y + cut_height) cols = 1 + (array_width - cut_width) // (cut_spacing_x + cut_width) via_params.setNumCutRows(rows) via_params.setNumCutCols(cols) custom_via.setViaParams(via_params) return custom_via def boxes2Rects(boxes, transform): rects = [] for box in boxes: ur = odb.Point(box.xMin(), box.yMin()) ll = odb.Point(box.xMax(), box.yMax()) transform.apply(ll) transform.apply(ur) pin_layer = box.getTechLayer() rects.append({"layer": pin_layer, "rect": odb.Rect(ll, ur)}) return rects def getInstObs(inst): master = inst.getMaster() master_ox, master_oy = master.getOrigin() inst_ox, inst_oy = inst.getOrigin() px, py = master_ox + inst_ox, master_oy + inst_oy orient = inst.getOrient() transform = odb.dbTransform(orient, odb.Point(px, py)) obstructions = master.getObstructions() rects = boxes2Rects(obstructions, transform) for rect in rects: rect["type"] = "obstruction" return rects def getITermBoxes(iterm): iterm_boxes = [] inst = iterm.getInst() mterm = iterm.getMTerm() master_ox, master_oy = inst.getMaster().getOrigin() inst_ox, inst_oy = inst.getOrigin() px, py = master_ox + inst_ox, master_oy + inst_oy orient = inst.getOrient() transform = odb.dbTransform(orient, odb.Point(px, py)) mpins = mterm.getMPins() if len(mpins) > 1: print( "Warning:", len(mpins), "mpins for iterm", inst.getName(), mterm.getName(), ) for i in range(len(mpins)): mpin = mpins[i] boxes = mpin.getGeometry() iterm_boxes += boxes2Rects(boxes, transform) # filter duplications # TODO: OpenDB bug? duplicate mpins for some reason iterm_boxes_set = set() iterm_boxes_uniq = [] for box in iterm_boxes: rect = box["rect"] llx, lly = rect.ll() urx, ury = rect.ur() set_item = (box["layer"].getName(), llx, lly, urx, ury) if set_item not in iterm_boxes_set: iterm_boxes_set.add(set_item) iterm_boxes_uniq.append(box) return iterm_boxes_uniq def getBiggestBoxAndIndex(boxes): biggest_area = -1 biggest_box = None biggest_i = -1 for i in range(len(boxes)): box = boxes[i] rect = box["rect"] area = rect.area() if area > biggest_area: biggest_area = area biggest_box = box biggest_i = i return biggest_box, biggest_i def rectOverlaps(rect1, rect2): return not (rect1.xMax() <= rect2.xMin() or rect1.xMin() >= rect2.xMax() or rect1.yMax() <= rect2.yMin() or rect1.yMin() >= rect2.yMax()) def rectMerge(rect1, rect2): rect = odb.Rect( min(rect1.xMin(), rect2.xMin()), min(rect1.yMin(), rect2.yMin()), max(rect1.xMax(), rect2.xMax()), max(rect1.yMax(), rect2.yMax()), ) return rect def getShapesOverlappingBBox(llx, lly, urx, ury, layers=[], ext_orient="R0"): shapes_overlapping = [] rect_bbox = odb.Rect(llx, lly, urx, ury) for box in ALL_BOXES: box_layer = box["layer"] ignore_box = len(layers) != 0 for layer in layers: if equalLayers(layer, box_layer): ignore_box = False break if not ignore_box: rect_box = transformRect(box["rect"], ext_orient) if rectOverlaps(rect_box, rect_bbox): shapes_overlapping.append(box) return shapes_overlapping def rectIntersection(rect1, rect2): rect = odb.Rect() if rectOverlaps(rect1, rect2): rect.set_xlo(max(rect1.xMin(), rect2.xMin())) rect.set_ylo(max(rect1.yMin(), rect2.yMin())) rect.set_xhi(min(rect1.xMax(), rect2.xMax())) rect.set_yhi(min(rect1.yMax(), rect2.yMax())) return rect def manhattanDistance(x1, y1, x2, y2): return odb.Point.manhattanDistance(odb.Point(x1, y1), odb.Point(x2, y2)) def center(x1, y1, x2, y2): return (x1 + x2) // 2, (y1 + y2) // 2 def gridify(rect): x1, y1 = rect.ll() x2, y2 = rect.ur() if (x2 - x1) % 2 != 0: x1 += 5 # 0.005 microns ! return odb.Rect(x1, y1, x2, y2) def forward(point, orient, distance): x, y = point.x(), point.y() if orient == "R0": point_forward = odb.Point(x, y - distance) elif orient == "R90": point_forward = odb.Point(x + distance, y) elif orient == "R180": point_forward = odb.Point(x, y + distance) elif orient == "R270": point_forward = odb.Point(x - distance, y) else: print("Unknown orientation") sys.exit(1) return point_forward def transformRect(rect, orient): transform = odb.dbTransform(orient) rect_ll = odb.Point(*rect.ll()) rect_ur = odb.Point(*rect.ur()) transform.apply(rect_ll) transform.apply(rect_ur) rect = odb.Rect(rect_ll, rect_ur) return rect def isObstruction(box): return box["type"] == "obstruction" def isCorePin(box): return box["type"] == "core_pin" def isStripe(box): return isCorePin(box) and box["stripe_flag"] def isPadPin(box): return box["type"] == "pad_pin" def isHighestRoutingLayer(layer): return layer.getRoutingLevel() == reader.tech.getRoutingLayerCount() def isLowestRoutingLayer(layer): return layer.getRoutingLevel() == 1 def isRoutingLayer(layer): return isinstance(layer, odb.dbTechLayer) and layer.getRoutingLevel() != 0 def getUpperRoutingLayer(layer): if not isRoutingLayer(layer): raise Exception( "Attempting to get upper routing layer of a non-routing layer") if isHighestRoutingLayer(layer): raise Exception( "Attempting to get upper routing layer of the highest routing layer" ) return layer.getUpperLayer().getUpperLayer() def getLowerRoutingLayer(layer): if not isRoutingLayer(layer): raise Exception( "Attempting to get lower routing layer of a non-routing layer") if isLowestRoutingLayer(layer): raise Exception( "Attempting to get lower routing layer of the lowest routing layer" ) return layer.getLowerLayer().getLowerLayer() def equalLayers(layer1, layer2): return layer1.getName() == layer2.getName() def layersBetween(layer1, layer2): # Inclusive layers_between = [layer1] if not equalLayers(layer1, layer2): if layer1.getRoutingLevel() < layer2.getRoutingLevel(): layer1 = getUpperRoutingLayer(layer1) while not equalLayers(layer1, layer2): layers_between.append(layer1) layer1 = getUpperRoutingLayer(layer1) else: layer1 = getLowerRoutingLayer(layer1) while not equalLayers(layer1, layer2): layers_between.append(layer1) layer1 = getLowerRoutingLayer(layer1) layers_between.append(layer2) return layers_between def createWireBox(rect_width, rect_height, rect_x, rect_y, layer, net, reorient="R0"): rect = odb.Rect(0, 0, rect_width, rect_height) rect.moveTo(rect_x, rect_y) rect = transformRect(rect, reorient) box = {"rect": rect, "layer": layer, "net": net} return box def getTechMaxSpacing(layers=reader.tech.getLayers()): max_spacing = -1 for layer in layers: max_spacing = max(max_spacing, layer.getSpacing()) # print("Max spacing for", layers, "is", max_spacing) return max_spacing print("Top-level design name:", reader.name) # create special nets for special_net_name in SPECIAL_NETS: net = odb.dbNet_create(reader.block, special_net_name) net.setSpecial() net.setWildConnected() wire = odb.dbSWire_create(net, "ROUTED") SPECIAL_NETS[special_net_name]["net"] = net SPECIAL_NETS[special_net_name]["wire"] = wire ALL_BOXES = [] # disconnect all iterms from anywhere else !!! (is this even needed?) for inst in reader.block.getInsts(): iterms = inst.getITerms() ALL_BOXES += getInstObs(inst) for iterm in iterms: master_name = inst.getMaster().getName() pin_name = iterm.getMTerm().getName() matched_special_net_name = None for special_net_name in SPECIAL_NETS: net = SPECIAL_NETS[special_net_name]["net"] for mapping in SPECIAL_NETS[special_net_name]["map"]: core_pin = SPECIAL_NETS[special_net_name]["core_pin"] pad_pin = mapping["pad_pin"] pad_name_substr = mapping["pad_name_substr"] if (pin_name == pad_pin and pad_name_substr in master_name): # pad pin matched_special_net_name = special_net_name print(inst.getName(), "connected to", net.getName()) # iterm.connect(net) # iterm.setSpecial() # get the pin shapes iterm_boxes = getITermBoxes(iterm) pad_orient = iterm.getInst().getOrient() for box in iterm_boxes: box["net"] = special_net_name box["type"] = "pad_pin" box["orient"] = pad_orient ALL_BOXES += iterm_boxes elif pin_name == core_pin: # macro ring matched_special_net_name = special_net_name print( inst.getName(), "will be connected to", master_name, "/", pin_name, ) # iterm.connect(net) # iterm.setSpecial() iterm_boxes = getITermBoxes(iterm) for box in iterm_boxes: box["net"] = special_net_name box["type"] = "core_pin" box["stripe_flag"] = False h_stripes = [] v_stripes = [] for i in range(4): # ASSUMPTION: 4 CORE RING-STRIPES biggest_pin_box, biggest_pin_box_i = getBiggestBoxAndIndex( iterm_boxes) if biggest_pin_box is None: continue del iterm_boxes[biggest_pin_box_i] biggest_pin_box["stripe_flag"] = True rect = biggest_pin_box["rect"] if rect.dx() > rect.dy(): # horizontal stripe biggest_pin_box["orient"] = "R90" h_stripes.append(biggest_pin_box) else: biggest_pin_box["orient"] = "R0" v_stripes.append(biggest_pin_box) if len(h_stripes) >= 2: if (h_stripes[0]["rect"].yMin() < h_stripes[1]["rect"].yMin()): h_stripes[0]["side"] = "S" h_stripes[1]["side"] = "N" else: h_stripes[0]["side"] = "N" h_stripes[1]["side"] = "S" elif len(h_stripes) == 1: print( "Warning: only one horizontal stripe found for", core_pin, ) if (reader.block.getBBox().yMax() - h_stripes[0]["rect"].yMin() > reader.block.getBBox().yMin() - h_stripes[0]["rect"].yMin()): h_stripes[0]["side"] = "S" else: h_stripes[0]["side"] = "N" else: print( "Warning: No horizontal stripes in the design!" ) if len(v_stripes) >= 2: if (v_stripes[0]["rect"].xMin() < v_stripes[1]["rect"].xMin()): v_stripes[0]["side"] = "W" v_stripes[1]["side"] = "E" else: v_stripes[0]["side"] = "E" v_stripes[1]["side"] = "W" elif len(v_stripes) == 1: print( "Warning: only one vertical stripe found for", core_pin) if (reader.block.getBBox().xMax() - v_stripes[0]["rect"].xMin() > reader.block.getBBox().xMin() - v_stripes[0]["rect"].xMin()): v_stripes[0]["side"] = "W" else: v_stripes[0]["side"] = "E" else: print( "Warning: No vertical stripes in the design!") ALL_BOXES += iterm_boxes + v_stripes + h_stripes if (matched_special_net_name is None): # other pins are obstructions for our purposes new_obstructions = getITermBoxes(iterm) for obs in new_obstructions: obs["type"] = "obstruction" ALL_BOXES += new_obstructions # only types are: obstruction, core_pin, pad_pin assert len({box["type"]: None for box in ALL_BOXES}) == 3 wire_boxes = [] PAD_PINS = [box for box in ALL_BOXES if isPadPin(box)] CORE_STRIPES = [box for box in ALL_BOXES if isStripe(box)] # Go over power pad pins, find the nearest ring-stripe, # check how to connect to it connections_count = 0 for box in PAD_PINS: pad_pin_orient = box["orient"] pad_pin_rect = box["rect"] ## # if connections_count > 29: # pprint(wire_boxes) # break ## nearest_stripe_box = None for stripe in CORE_STRIPES: if (box["net"] == stripe["net"] and stripe["side"] == ORIENT_LOC_MAP[pad_pin_orient]): nearest_stripe_box = stripe if nearest_stripe_box is None: print( "Pad pin at", box["rect"].ll(), box["rect"].ur(), "doesn't have a facing stripe. Skipping.", ) continue stripe_rect = nearest_stripe_box["rect"] # TRANSFORM TO R0 ORIENTATION pad_pin_orient_inv = "R" + str((360 - int(pad_pin_orient[1:])) % 360) assert pad_pin_orient_inv in ORIENT_LOC_MAP pad_pin_rect = transformRect(pad_pin_rect, pad_pin_orient_inv) stripe_rect = transformRect(stripe_rect, pad_pin_orient_inv) projection_x_min, projection_x_max = max(pad_pin_rect.xMin(), stripe_rect.xMin()), min( stripe_rect.xMax(), pad_pin_rect.xMax()) MIN_WIDTH = 5000 if projection_x_max - projection_x_min < MIN_WIDTH: continue # account for obstructions from_layer = box["layer"] to_layer = nearest_stripe_box["layer"] # prefer the highest possible connecting layer (antenna reasons) connecting_layer = from_layer if not isHighestRoutingLayer(to_layer): connecting_layer = getUpperRoutingLayer(to_layer) elif not isLowestRoutingLayer(to_layer): connecting_layer = getLowerRoutingLayer(to_layer) print("Connecting with", connecting_layer.getName(), "to", to_layer.getName()) wire_rect = odb.Rect(projection_x_min, pad_pin_rect.yMin(), projection_x_max, stripe_rect.yMin()) # adjust width MAX_SPACING = getTechMaxSpacing( (set(layersBetween(from_layer, connecting_layer)).union( layersBetween(connecting_layer, to_layer)))) # note that the box is move away from the pad to evade interactions with # nearby pads overlapping_boxes = getShapesOverlappingBBox( wire_rect.xMin() - MAX_SPACING, wire_rect.yMin(), wire_rect.xMax() + MAX_SPACING, wire_rect.yMax() - MAX_SPACING, ext_orient=pad_pin_orient_inv, layers=list( set(layersBetween(connecting_layer, to_layer)) - set([to_layer])), ) # find the new possible wire_rect width after subtracting the obstructions skip = False obs_boundaries = [] for ov_box in overlapping_boxes: obs_rect = transformRect(ov_box["rect"], pad_pin_orient_inv) # if it is completely contained in an obstruction if (obs_rect.xMin() <= wire_rect.xMin() < wire_rect.xMax() <= obs_rect.xMax()): skip = True break if (wire_rect.xMin() - MAX_SPACING <= obs_rect.xMin() <= wire_rect.xMax() + MAX_SPACING): obs_boundaries.append(obs_rect.xMin()) if (wire_rect.xMin() - MAX_SPACING <= obs_rect.xMax() <= wire_rect.xMax() + MAX_SPACING): obs_boundaries.append(obs_rect.xMax()) obs_boundaries.sort() if len(obs_boundaries) > 0: obs_min_x = obs_boundaries[0] obs_max_x = obs_boundaries[-1] print("Adjusting", wire_rect.ll(), wire_rect.ur()) print(obs_max_x - obs_min_x) print(obs_min_x, obs_max_x) if (obs_min_x - (wire_rect.xMin() - MAX_SPACING) > (wire_rect.xMax() + MAX_SPACING) - obs_max_x): wire_rect.set_xhi(obs_min_x - MAX_SPACING) else: wire_rect.set_xlo(obs_max_x + MAX_SPACING) print("To", wire_rect.ll(), wire_rect.ur()) # leave some space for routing from_layer_space = 2 * from_layer.getSpacing() + from_layer.getWidth() wire_width = wire_rect.dx() - from_layer_space wire_x = wire_rect.xMin() + from_layer_space wire_y = wire_rect.yMax() if wire_width < MIN_WIDTH: skip = True if skip: continue basic_rect = rectIntersection(wire_rect, stripe_rect) # 1. extend outside by half of the size of the "basic rectangle" wire_y -= basic_rect.dy() // 2 wire_boxes.append( createWireBox( wire_width, basic_rect.dy() // 2, wire_x, wire_y, from_layer, box["net"], reorient=pad_pin_orient, )) # 2. vias up till the connecting_layer on the next basic rect wire_y -= basic_rect.dy() prev_layer = None for layer in layersBetween(from_layer, connecting_layer): if prev_layer is not None: via = create_custom_via( prev_layer, layer, wire_width, basic_rect.dy(), reorient=pad_pin_orient, ) wire_boxes.append( createWireBox( wire_width, basic_rect.dy(), wire_x, wire_y, via, box["net"], reorient=pad_pin_orient, )) wire_boxes.append( createWireBox( wire_width, basic_rect.dy(), wire_x, wire_y, layer, box["net"], reorient=pad_pin_orient, )) prev_layer = layer # 3. use the connecting layer to connect to the stripe wire_height = wire_y - wire_rect.yMin() wire_y = wire_rect.yMin() wire_boxes.append( createWireBox( wire_width, wire_height, wire_x, wire_y, connecting_layer, box["net"], reorient=pad_pin_orient, )) # 4. vias down from the connecting_layer to the to_layer (stripe layer) prev_layer = None for layer in layersBetween(connecting_layer, to_layer): if prev_layer is not None: via = create_custom_via( prev_layer, layer, wire_width, basic_rect.dy(), reorient=pad_pin_orient, ) wire_boxes.append( createWireBox( wire_width, basic_rect.dy(), wire_x, wire_y, via, box["net"], reorient=pad_pin_orient, )) wire_boxes.append( createWireBox( wire_width, basic_rect.dy(), wire_x, wire_y, layer, box["net"], reorient=pad_pin_orient, )) prev_layer = layer connections_count += 1 # ROUTING print("creating", len(wire_boxes), "wires") for box in wire_boxes: net_name = box["net"] print(net_name, ": drawing a special wire on layer", box["layer"].getName()) rect = gridify(box["rect"]) if isRoutingLayer(box["layer"]): odb.dbSBox_create( SPECIAL_NETS[net_name]["wire"], box["layer"], *rect.ll(), *rect.ur(), "COREWIRE", odb.dbSBox.UNDEFINED, ) else: odb.dbSBox_create( SPECIAL_NETS[net_name]["wire"], box["layer"], (rect.xMin() + rect.xMax()) // 2, (rect.yMin() + rect.yMax()) // 2, "COREWIRE", ) SPECIAL_NETS[net_name]["covered"] = True uncovered_nets = [ net_name for net_name in SPECIAL_NETS if not SPECIAL_NETS[net_name]["covered"] ] if len(uncovered_nets) > 0: print("No routes created on the following nets:") print(uncovered_nets) print("Make sure the pads to be connected are facing the core rings") sys.exit(1) # OUTPUT odb.write_def(reader.block, output) odb.write_lef(odb.dbLib_getLib(reader.db, 1), f"{output}.lef")