blob: 81d7f4e61f4c5079cda9d6f64a98652a01ff89ad [file] [log] [blame]
# 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.
"""
Places the IOs according to an input file. Supports regexes.
File format:
#N|#S|#E|#W
pin1_regex
pin2_regex
...
#S|#N|#E|#W
...
...
"""
import odb
import os
import re
import sys
import click
import random
@click.command()
@click.option("-l", "--input-lef", required=True, help="Input merged tlef/lef file.")
@click.option(
"-o",
"--output-def",
default="./output.def",
help="Output DEF file with newly placed pins",
)
@click.option("-c", "--config", required=False, help="Optional configuration file.")
@click.option(
"-r",
"--reverse",
default="",
type=str,
help="Reverse along comma,delimited,cardinals: e.g. N,E",
)
@click.option("-L", "--length", default=2, type=float, help="Pin length in microns.")
@click.option(
"-V",
"--ver-layer",
required=True,
help="Name of metal layer to place vertical pins on.",
)
@click.option(
"-H",
"--hor-layer",
required=True,
help="Name of metal layer to place horizontal pins on.",
)
@click.option(
"--hor-extension",
default=0,
type=float,
help="Extension for vertical pins in microns.",
)
@click.option(
"--ver-extension",
default=0,
type=float,
help="Extension for horizontal pins in microns.",
)
@click.option(
"--ver-width-mult", default=2, type=float, help="Multiplier for vertical pins."
)
@click.option(
"--hor-width-mult", default=2, type=float, help="Multiplier for horizontal pins."
)
@click.option(
"--bus-sort/--no-bus-sort",
default=False,
help="Misnomer: pins are grouped by index instead of bus, i.e. a[0] goes with b[0] instead of a[1].",
)
@click.argument("input_def")
def cli(
input_lef,
output_def,
config,
ver_layer,
hor_layer,
ver_width_mult,
hor_width_mult,
length,
hor_extension,
ver_extension,
reverse,
bus_sort,
input_def,
):
"""
Places the IOs in an input def with an optional config file that supports regexes.
Config format:
#N|#S|#E|#W
pin1_regex (low co-ordinates to high co-ordinates; e.g., bottom to top and left to right)
pin2_regex
...
#S|#N|#E|#W
"""
def_file_name = input_def
lef_file_name = input_lef
output_def_file_name = output_def
config_file_name = config
bus_sort_flag = bus_sort
#1. Manual Pad Placement - Dinesh A
manual_place_flag = False
h_layer_name = hor_layer
v_layer_name = ver_layer
h_width_mult = float(hor_width_mult)
v_width_mult = float(ver_width_mult)
# Initialize OpenDB
db_top = odb.dbDatabase.create()
odb.read_lef(db_top, lef_file_name)
odb.read_def(db_top, def_file_name)
block = db_top.getChip().getBlock()
micron_in_units = block.getDefUnits()
LENGTH = int(micron_in_units * length)
H_EXTENSION = int(micron_in_units * hor_extension)
V_EXTENSION = int(micron_in_units * ver_extension)
if H_EXTENSION < 0:
H_EXTENSION = 0
if V_EXTENSION < 0:
V_EXTENSION = 0
reverse_arr_raw = reverse.split(",")
reverse_arr = []
for element in reverse_arr_raw:
if element.strip() != "":
reverse_arr.append(f"#{element}")
def getGrid(origin, count, step):
tracks = []
pos = origin
for i in range(count):
tracks.append(pos)
pos += step
assert len(tracks) > 0
tracks.sort()
return tracks
def equallySpacedSeq(m, arr):
seq = []
n = len(arr)
# Bresenham
indices = [i * n // m + n // (2 * m) for i in range(m)]
for i in indices:
seq.append(arr[i])
return seq
# HUMAN SORTING: https://stackoverflow.com/questions/5967500/how-to-correctly-sort-a-string-with-a-number-inside
def natural_keys(enum):
def atof(text):
try:
retval = float(text)
except ValueError:
retval = text
return retval
text = enum[0]
text = re.sub(r"(\[|\]|\.|\$)", "", text)
"""
alist.sort(key=natural_keys) sorts in human order
http://nedbatchelder.com/blog/200712/human_sorting.html
(see toothy's implementation in the comments)
float regex comes from https://stackoverflow.com/a/12643073/190597
"""
return [
atof(c) for c in re.split(r"[+-]?([0-9]+(?:[.][0-9]*)?|[.][0-9]+)", text)
]
def bus_keys(enum):
text = enum[0]
m = re.match(r"^.*\[(\d+)\]$", text)
if not m:
return -1
else:
return int(m.group(1))
#2. Find the Slot matching next nearest slot-DineshA
def findSlot(val, arr):
for i in arr:
if(i > val):
return i
print("ERROR: Next Valid Position not found :",val)
return -1
# read config
pin_placement_cfg = {"#N": [], "#E": [], "#S": [], "#W": []}
cur_side = None
if config_file_name is not None and config_file_name != "":
with open(config_file_name, "r") as config_file:
for line in config_file:
line = line.split()
if len(line) == 0:
continue
#3. Dinesh A - Start
if(manual_place_flag == False):
if len(line) > 1:
print("Only one entry allowed per line.")
sys.exit(1)
token = line[0]
else:
#During Manual Place we are allowing Four field
# <Pad Name> <Offset> <Position> <Multiplier>
# Causion: Make sure that you have given absolute name, else it will give issue
if len(line) > 4:
print("Only Four entry allowed per line.")
sys.exit(1)
if line[0] not in ["#N", "#E", "#S", "#W", "#NR", "#ER", "#SR", "#WR"]:
token = line
else:
token = line[0]
if cur_side is not None and token[0] != "#":
pin_placement_cfg[cur_side].append(token)
elif token not in [
"#N",
"#E",
"#S",
"#W",
"#NR",
"#ER",
"#SR",
"#WR",
"#BUS_SORT",
"#MANUAL_PLACE"
]:
print(
"Valid directives are #N, #E, #S, or #W. Append R for reversing the default order.",
"Use #BUS_SORT to group 'bus bits' by index.",
"Please make sure you have set a valid side first before listing pins",
)
sys.exit(1)
elif token == "#BUS_SORT":
bus_sort_flag = True
#4 - Dinesh A
elif token == "#MANUAL_PLACE":
print("Input token ",token)
manual_place_flag = True
else:
if len(token) == 3:
token = token[0:2]
reverse_arr.append(token)
cur_side = token
# build a list of pins
chip_top = db_top.getChip()
block_top = chip_top.getBlock()
top_design_name = block_top.getName()
tech = db_top.getTech()
H_LAYER = tech.findLayer(h_layer_name)
V_LAYER = tech.findLayer(v_layer_name)
H_WIDTH = int(h_width_mult * H_LAYER.getWidth())
V_WIDTH = int(v_width_mult * V_LAYER.getWidth())
print("Top-level design name:", top_design_name)
bterms = block_top.getBTerms()
bterms_enum = []
for bterm in bterms:
pin_name = bterm.getName()
bterms_enum.append((pin_name, bterm))
# sort them "humanly"
bterms_enum.sort(key=natural_keys)
if bus_sort_flag:
bterms_enum.sort(key=bus_keys)
bterms = [bterm[1] for bterm in bterms_enum]
pin_placement = {"#N": [], "#E": [], "#S": [], "#W": []}
bterm_regex_map = {}
#5. Dinesh A
if(manual_place_flag == False):
for side in pin_placement_cfg:
for regex in pin_placement_cfg[side]: # going through them in order
regex += "$" # anchor
for bterm in bterms:
# if a pin name matches multiple regexes, their order will be
# arbitrary. More refinement requires more strict regexes (or just
# the exact pin name).
pin_name = bterm.getName()
if re.match(regex, pin_name) is not None:
if bterm in bterm_regex_map:
print(
"Error: Multiple regexes matched",
pin_name,
". Those are",
bterm_regex_map[bterm],
"and",
regex,
)
sys.exit(os.EX_DATAERR)
bterm_regex_map[bterm] = regex
pin_placement[side].append(bterm) # to maintain the order
unmatched_bterms = [bterm for bterm in bterms if bterm not in bterm_regex_map]
if len(unmatched_bterms) > 0:
print("Warning: Some pins weren't matched by the config file")
print("Those are:", [bterm.getName() for bterm in unmatched_bterms])
if True:
print("Assigning random sides to the above pins")
for bterm in unmatched_bterms:
random_side = random.choice(list(pin_placement.keys()))
pin_placement[random_side].append(bterm)
else:
sys.exit(1)
#6 Dinesh A
else:
for side in pin_placement_cfg:
for regex in pin_placement_cfg[side]: # going through them in order
regex = regex[0] # take first value
regex += "$" # anchor
for bterm in bterms:
# if a pin name matches multiple regexes, their order will be
# arbitrary. More refinement requires more strict regexes (or just
# the exact pin name).
pin_name = bterm.getName()
if re.match(regex, pin_name) is not None:
print("Debug: Serching Pin match",regex)
if bterm in bterm_regex_map:
#print("Warning: Multiple regexes matched", pin_name)
# ". Those are", bterm_regex_map[bterm], "and", regex)
sys.exit(1)
bterm_regex_map[bterm] = regex
pin_placement[side].append(bterm) # to maintain the order
unmatched_bterms = [bterm for bterm in bterms if bterm not in bterm_regex_map]
if len(unmatched_bterms) > 0:
print("Warning: Some pins weren't matched by the config file")
print("Those are:", [bterm.getName() for bterm in unmatched_bterms])
sys.exit(1)
assert len(block_top.getBTerms()) == len(
pin_placement["#N"]
+ pin_placement["#E"]
+ pin_placement["#S"]
+ pin_placement["#W"]
)
# generate slots
DIE_AREA = block_top.getDieArea()
BLOCK_LL_X = DIE_AREA.xMin()
BLOCK_LL_Y = DIE_AREA.yMin()
BLOCK_UR_X = DIE_AREA.xMax()
BLOCK_UR_Y = DIE_AREA.yMax()
print("Block boundaries:", BLOCK_LL_X, BLOCK_LL_Y, BLOCK_UR_X, BLOCK_UR_Y)
origin, count, step = block_top.findTrackGrid(H_LAYER).getGridPatternY(0)
#7. Save the horizontal origin and step - DineshA
h_origin = origin
h_step = step
h_tracks = getGrid(origin, count, step)
origin, count, step = block_top.findTrackGrid(V_LAYER).getGridPatternX(0)
#8. Save the horizontal origin and step - DineshA
v_origin = origin
v_step = step
v_tracks = getGrid(origin, count, step)
for rev in reverse_arr:
pin_placement[rev].reverse()
# create the pins
#9. DineshA
if(manual_place_flag == False):
for side in pin_placement:
if side in ["#N", "#S"]:
slots = equallySpacedSeq(len(pin_placement[side]), v_tracks)
else:
slots = equallySpacedSeq(len(pin_placement[side]), h_tracks)
assert len(slots) == len(pin_placement[side])
for i in range(len(pin_placement[side])):
bterm = pin_placement[side][i]
slot = slots[i]
pin_name = bterm.getName()
pins = bterm.getBPins()
if len(pins) > 0:
print("Warning:", pin_name, "already has shapes. Modifying them")
assert len(pins) == 1
pin_bpin = pins[0]
else:
pin_bpin = odb.dbBPin_create(bterm)
pin_bpin.setPlacementStatus("PLACED")
if side in ["#N", "#S"]:
rect = odb.Rect(0, 0, V_WIDTH, LENGTH + V_EXTENSION)
if side == "#N":
y = BLOCK_UR_Y - LENGTH
else:
y = BLOCK_LL_Y - V_EXTENSION
rect.moveTo(slot - V_WIDTH // 2, y)
odb.dbBox_create(pin_bpin, V_LAYER, *rect.ll(), *rect.ur())
else:
rect = odb.Rect(0, 0, LENGTH + H_EXTENSION, H_WIDTH)
if side == "#E":
x = BLOCK_UR_X - LENGTH
else:
x = BLOCK_LL_X - H_EXTENSION
rect.moveTo(x, slot - H_WIDTH // 2)
odb.dbBox_create(pin_bpin, H_LAYER, *rect.ll(), *rect.ur())
else:
#10.New Logic, Manual Pin Placement - Dinesh A
#print("Allowed VTracks",v_tracks)
#print("Allowed hTracks",h_tracks)
for side in pin_placement:
if(len(pin_placement[side]) != len(pin_placement_cfg[side])):
print("ERROR : At Side:", side, " Total Pin Defined ",len(pin_placement_cfg[side]), "More than available:",len(pin_placement[side]))
for regex in pin_placement_cfg[side]:
regex = regex[0] # take first value
print("Pins in pin_placement_cfg: " , regex)
for regex in pin_placement[side]:
print("Pins in pin_placement: " , regex)
#check defined pad are more than avaibale one
assert len(pin_placement[side]) == len(pin_placement_cfg[side])
start = 0
start_loc = 0
pad_pos = 0
slot_pre = 0
#Dinesh: Give Step Multipler size *2 for better pad placement
multiplier= 2
for i in range(len(pin_placement_cfg[side])):
#Dinesh: Multiply the offset by 1000 for micro conversion
if(len(pin_placement_cfg[side][i]) > 1):
start_loc = int(pin_placement_cfg[side][i][1])
if(len(pin_placement_cfg[side][i]) > 2):
pad_pos = int(pin_placement_cfg[side][i][2])
if(len(pin_placement_cfg[side][i]) > 3):
multiplier = int(pin_placement_cfg[side][i][3])
if side in ["#N", "#S"]:
slott = start_loc*1000+int(v_origin)+(int(v_step) * pad_pos * multiplier)
slot =findSlot(slott,v_tracks)
else:
slott = start_loc*1000+int(h_origin)+(int(h_step) * pad_pos * multiplier)
slot =findSlot(slott,h_tracks)
pad_pos +=1
bterm = pin_placement[side][i]
pin_name = bterm.getName()
pins = bterm.getBPins()
if len(pins) > 0:
print("Warning:", pin_name, "already has shapes. Modifying them")
assert len(pins) == 1
pin_bpin = pins[0]
else:
pin_bpin = odb.dbBPin_create(bterm)
if(slot < slot_pre):
print("ERROR:", "Current Pad:", pin_name, " Slot:" , slot, " is less than Previous One:",slot_pre)
sys.exit(1)
slot_pre = slot
print("Dinesh: Placing Pad:" ,pin_name, " At Side: ", side, " Slot: ", slot)
pin_bpin.setPlacementStatus("PLACED")
if side in ["#N", "#S"]:
rect = odb.Rect(0, 0, V_WIDTH, LENGTH+V_EXTENSION)
if side == "#N":
y = BLOCK_UR_Y-LENGTH
else:
y = BLOCK_LL_Y-V_EXTENSION
rect.moveTo(slot-V_WIDTH//2, y)
odb.dbBox_create(pin_bpin, V_LAYER, *rect.ll(), *rect.ur())
else:
rect = odb.Rect(0, 0, LENGTH+H_EXTENSION, H_WIDTH)
if side == "#E":
x = BLOCK_UR_X-LENGTH
else:
x = BLOCK_LL_X-H_EXTENSION
rect.moveTo(x, slot-H_WIDTH//2)
odb.dbBox_create(pin_bpin, H_LAYER, *rect.ll(), *rect.ur())
print(
f"Writing {output_def_file_name}...",
)
odb.write_def(block_top, output_def_file_name)
if __name__ == "__main__":
cli()