| # 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() |