blob: 7d1be99e605cc8910a7b982099510a995bfdbc9e [file] [log] [blame]
#!/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")