| #!/usr/bin/env python3 |
| # Copyright 2020 Efabless Corporation |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| """ |
| Takes a pre-routed top-level layout and creates the pg connections |
| from the power/ground pads to the *core ring* |
| """ |
| |
| import sys |
| import argparse |
| import odb |
| |
| parser = argparse.ArgumentParser( |
| description="Produces a DEF file with VDD and GND special nets\ |
| where the power pads are connected to the core ring" |
| ) |
| |
| parser.add_argument( |
| "--input-def", "-d", required=True, help="DEF view of the design pre-routing" |
| ) |
| |
| parser.add_argument( |
| "--input-lef", |
| "-l", |
| required=True, |
| help="LEF file needed to have a proper view of the DEF", |
| ) |
| |
| parser.add_argument( |
| "--core-vdd-pin", |
| "-cvdd", |
| required=True, |
| help="Name of the power pin of core macro (exposed as its core ring)", |
| ) |
| |
| parser.add_argument( |
| "--core-gnd-pin", |
| "-cgnd", |
| required=True, |
| help="Name of the ground pin of core macro (exposed as its core ring)", |
| ) |
| |
| |
| parser.add_argument( |
| "--output-def", |
| "-o", |
| required=True, |
| default="output.def", |
| help="Output power-routed DEF", |
| ) |
| |
| parser.add_argument( |
| "--vdd-pad-pin-map", |
| "-vmap", |
| action="append", |
| nargs=2, |
| required=False, |
| default=None, |
| help="Mappings to mark PAD-pin pairs as part of the VDD net. Defaults to sky130 settings.", |
| ) |
| |
| parser.add_argument( |
| "--gnd-pad-pin-map", |
| "-gmap", |
| action="append", |
| nargs=2, |
| required=False, |
| default=None, |
| help="Mappings to mark PAD-pin pairs as part of the GND net. Defaults to sky130 settings.", |
| ) |
| |
| |
| args = parser.parse_args() |
| |
| def_file_name = args.input_def |
| lef_file_name = args.input_lef |
| output_def_file_name = args.output_def |
| core_vdd_pin = args.core_vdd_pin |
| core_gnd_pin = args.core_gnd_pin |
| vdd_pad_pin_map = args.vdd_pad_pin_map |
| gnd_pad_pin_map = args.gnd_pad_pin_map |
| |
| |
| # 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 |
| |
| |
| ################## |
| |
| db_top = odb.dbDatabase.create() |
| odb.read_lef(db_top, lef_file_name) |
| odb.read_def(db_top, def_file_name) |
| |
| chip_top = db_top.getChip() |
| block_top = chip_top.getBlock() |
| top_design_name = block_top.getName() |
| tech = db_top.getTech() |
| |
| via_rules = 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 = block_top.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(block_top, 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() == 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=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: |
| 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 ( |
| block_top.getBBox().yMax() - h_stripes[0]["rect"].yMin() |
| > block_top.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 ( |
| block_top.getBBox().xMax() - v_stripes[0]["rect"].xMin() |
| > block_top.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_layer = box["layer"] |
| 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) |
| |
| pad_pin_width = pad_pin_rect.dx() |
| |
| 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(block_top, output_def_file_name) |
| |
| odb.write_lef(odb.dbLib_getLib(db_top, 1), output_def_file_name + ".lef") |
| |
| print("Done") |