def random_place(output, input_lef, input_def): reader = OdbReader(input_lef, input_def) core_area = reader.block.getCoreArea() LLX, LLY = core_area.ll() URX, URY = core_area.ur() insts = reader.block.getInsts() print("Design name:", reader.name) print("Core Area Boundaries:", LLX, LLY, URX, URY) print("Number of instances", len(insts)) placed_cnt = 0 for inst in insts: if inst.isFixed(): continue master = inst.getMaster() master_width = master.getWidth() master_height = master.getHeight() x = gridify(random.randint(LLX, max(LLX, URX - master_width)), 5) y = gridify(random.randint(LLY, max(LLY, URY - master_height)), 5) inst.setLocation(x, y) inst.setPlacementStatus("PLACED") placed_cnt += 1 print(f"Placed {placed_cnt} instances.") odb.write_def(reader.block, output)
def add_def_obstructions(output, obstructions, input_lef, input_def): reader = OdbReader(input_lef, input_def) RE_NUMBER = r"[\-]?[0-9]+(\.[0-9]+)?" RE_OBS = (r"(?P<layer>\S+)\s+" + r"(?P<bbox>" + RE_NUMBER + r"\s+" + RE_NUMBER + r"\s+" + RE_NUMBER + r"\s+" + RE_NUMBER + r")") obses = obstructions.split(",") obs_list = [] for obs in obses: obs = obs.strip() m = re.match(RE_OBS, obs) assert ( m ), "Incorrectly formatted input (%s).\n Format: layer llx lly urx ury, ..." % ( obs) layer = m.group("layer") bbox = [float(x) for x in m.group("bbox").split()] obs_list.append((layer, bbox)) for obs in obs_list: layer = obs[0] bbox = obs[1] dbu = reader.tech.getDbUnitsPerMicron() bbox = [int(x * dbu) for x in bbox] print("Creating an obstruction on", layer, "at", *bbox, "(DBU)") odb.dbObstruction_create(reader.block, reader.tech.findLayer(layer), *bbox) odb.write_def(reader.block, output)
def replace_fake(fake_diode, true_diode, violations_file, output, input_lef, input_def): reader = OdbReader(input_lef, input_def) violations = open(violations_file).read().strip().split() diode = [ sc for sc in reader.lef.getMasters() if sc.getName() == true_diode ] antenna_rx = re.compile(r"ANTENNA_(\s+)_.*") instances = reader.block.getInsts() for instance in instances: name: str = instance.getName() m = antenna_rx.match(name) if m is None: continue if m[1] not in violations: continue master_name = instance.getMaster().getName() if master_name == fake_diode: instance.swapMasters(diode) assert odb.write_def(reader.block, output) == 1
def mark_component_fixed(cell_name, output, input_lef, input_def): reader = OdbReader(input_lef, input_def) instances = reader.block.getInsts() for instance in instances: if instance.getMaster().getName() == cell_name: instance.setPlacementStatus("FIRM") assert odb.write_def(reader.block, output) == 1
def extract_core_dims(output_data, input_lef, input_def): reader = OdbReader(input_lef, input_def) core_area = reader.block.getCoreArea() with open(output_data, "w") as f: print( f"{core_area.dx() / reader.dbunits} {core_area.dy() / reader.dbunits}", file=f, )
def replace_instance_prefixes(original_prefix, new_prefix, output, input_lef, input_def): reader = OdbReader(input_lef, input_def) for instance in reader.block.getInsts(): name: str = instance.getName() if name.startswith(f"{original_prefix}_"): new_name = name.replace(f"{original_prefix}_", f"{new_prefix}_") instance.rename(new_name) assert odb.write_def(reader.block, output) == 1
def remove_pins(rx, pin_name, output, input_lef, input_def): reader = OdbReader(input_lef, input_def) matcher = re.compile(pin_name if rx else f"^{re.escape(pin_name)}$") pins = reader.block.getBTerms() for pin in pins: name = pin.getName() name_m = matcher.search(name) if name_m is not None: odb.dbBTerm.destroy(pin) assert odb.write_def(reader.block, output) == 1
def remove_components(rx, instance_name, output, input_lef, input_def): reader = OdbReader(input_lef, input_def) matcher = re.compile( instance_name if rx else f"^{re.escape(instance_name)}$") instances = reader.block.getInsts() for instance in instances: name = instance.getName() name_m = matcher.search(name) if name_m is not None: odb.dbInst.destroy(instance) assert odb.write_def(reader.block, output) == 1
def manual_macro_place(output, config, fixed, input_lef, input_def): """ Places macros in positions and orientations specified by a config file """ reader = OdbReader(input_lef, input_def) # read config macros = {} with open(config, "r") as config_file: for line in config_file: # Discard comments and empty lines line = line.split("#")[0].strip() if not line: continue line = line.split() macros[line[0]] = [ str(int(float(line[1]) * 1000)), str(int(float(line[2]) * 1000)), line[3], ] print("Placing the following macros:") print(macros) print("Design name:", reader.name) macros_cnt = len(macros) for inst in reader.block.getInsts(): inst_name = inst.getName() if inst.isFixed(): assert inst_name not in macros, inst_name continue if inst_name in macros: print("Placing", inst_name) macro_data = macros[inst_name] x = gridify(int(macro_data[0]), 5) y = gridify(int(macro_data[1]), 5) inst.setOrient(lef_rot_to_oa_rot(macro_data[2])) inst.setLocation(x, y) if fixed: inst.setPlacementStatus("FIRM") else: inst.setPlacementStatus("PLACED") del macros[inst_name] assert not macros, ("Macros not found:", macros) print("Successfully placed", macros_cnt, "instances") odb.write_def(reader.block, output) assert os.path.exists(output), "Output not written successfully"
def get_metal_layers(output, lefs): reader = OdbReader(lefs, None) layers = [ layer for layer in reader.tech.getLayers() if layer.getRoutingLevel() >= 1 ] layer_names = [ layer.getName() for layer in sorted(layers, key=lambda l: l.getRoutingLevel()) ] with open(output, "w") as f: f.write(" ".join(layer_names)) print(layer_names)
def remove_nets(rx, net_name, output, empty_only, input_lef, input_def): reader = OdbReader(input_lef, input_def) matcher = re.compile(net_name if rx else f"^{re.escape(net_name)}$") nets = reader.block.getNets() for net in nets: name = net.getName() name_m = matcher.search(name) if name_m is not None: if empty_only and len(net.getITerms()) > 0: continue # BTerms = PINS, if it has a pin we need to keep the net if len(net.getBTerms()) > 0: for port in net.getITerms(): odb.dbITerm.disconnect(port) else: odb.dbNet.destroy(net) assert odb.write_def(reader.block, output) == 1
def place( output, input_lef, verbose, diode_cell, fake_diode_cell, diode_pin, side_strategy, port_protect, short_span, input_def, ): reader = OdbReader(input_lef, input_def) print(f"Design name: {reader.name}") pp_val = { "none": [], "in": ["INPUT"], "out": ["OUTPUT"], "both": ["INPUT", "OUTPUT"], } di = DiodeInserter( reader.block, diode_cell=diode_cell, diode_pin=diode_pin, fake_diode_cell=fake_diode_cell, side_strategy=side_strategy, short_span=short_span, port_protect=pp_val[port_protect], verbose=verbose, ) di.execute() print("Inserted", len(di.inserted), "diodes.") # Write result odb.write_def(reader.block, output)
def label_macro_pins( netlist_def, verbose, all_shapes, pad_pin_name, pin_size, map, output, input_lef, input_def, ): """ Takes a DEF file with no PINS section, a LEF file that has the shapes of all i/o ports that need to labeled, and a netlist where i/o ports are connected to single macro pin given also as an input -> writes a PINS section with shapes generated over those macro pins, "labels". """ extra_mappings = [tuple(m.split()) for m in map.split(";")] extra_mappings_pin_names = [tup[2] for tup in extra_mappings] def getBiggestBox(iterm): inst = iterm.getInst() px, py = inst.getOrigin() orient = inst.getOrient() transform = odb.dbTransform(orient, odb.Point(px, py)) mterm = iterm.getMTerm() mpins = mterm.getMPins() # label biggest mpin biggest_mpin = None biggest_size = -1 for i in range(len(mpins)): mpin = mpins[i] box = mpin.getGeometry()[ 0] # assumes there's only one; to extend and get biggest llx, lly = box.xMin(), box.yMin() urx, ury = box.xMax(), box.yMax() area = (urx - llx) * (ury - lly) if area > biggest_size: biggest_size = area biggest_mpin = mpin main_mpin = biggest_mpin box = main_mpin.getGeometry()[0] ll = odb.Point(box.xMin(), box.yMin()) ur = odb.Point(box.xMax(), box.yMax()) # x = (ll.getX() + ur.getX())//2 # y = (ll.getY() + ur.getY())//2 # if VERBOSE: print(x, y) transform.apply(ll) transform.apply(ur) layer = box.getTechLayer() return [(layer, ll, ur)] def getAllBoxes(iterm): inst = iterm.getInst() px, py = inst.getOrigin() orient = inst.getOrient() transform = odb.dbTransform(orient, odb.Point(px, py)) mterm = iterm.getMTerm() mpins = mterm.getMPins() boxes = [] for i in range(len(mpins)): mpin = mpins[i] geometries = mpin.getGeometry() for box in geometries: # llx, lly = box.xMin(), box.yMin() # urx, ury = box.xMax(), box.yMax() ll = odb.Point(box.xMin(), box.yMin()) ur = odb.Point(box.xMax(), box.yMax()) transform.apply(ll) transform.apply(ur) layer = box.getTechLayer() boxes.append((layer, ll, ur)) return boxes def labelITerm(top, pad_iterm, pin_name, iotype, all_shapes_flag=False): net_name = pin_name net = top.block.findNet(net_name) if net is None: net = odb.dbNet_create(top.block, net_name) pin_bterm = top.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) top = OdbReader(input_lef, input_def) mapping = OdbReader(input_lef, netlist_def) pad_pin_map = {} for net in mapping.block.getNets(): iterms = net.getITerms() bterms = net.getBTerms() if len(iterms) >= 1 and len(bterms) == 1: pin_name = bterms[0].getName() if pin_name in extra_mappings_pin_names: if verbose: print(pin_name, "handled by an external mapping; skipping...") continue pad_name = None pad_pin_name = None for iterm in iterms: iterm_pin_name = iterm.getMTerm().getName() if iterm_pin_name == pad_pin_name: pad_name = iterm.getInst().getName() pad_pin_name = iterm_pin_name break # '\[' and '\]' are common DEF names if pad_name is None: print( "Warning: net", net.getName(), "has a BTerm but no ITerms that match PAD_PIN_NAME", ) print("Warning: will label the first ITerm on the net!!!!!!!") pad_name = iterms[0].getInst().getName() pad_pin_name = iterms[0].getMTerm().getName() if verbose: print( "Labeling ", net.getName(), "(", pin_name, "-", pad_name, "/", pad_pin_name, ")", ) pad_pin_map.setdefault(pad_name, []) pad_pin_map[pad_name].append( (pad_pin_name, pin_name, bterms[0].getIoType())) for mapping in extra_mappings: pad_pin_map.setdefault(mapping[0], []) pad_pin_map[mapping[0]].append((mapping[1], mapping[2], mapping[3])) pad_pins_to_label_count = len([ mapping for sublist in [pair[1] for pair in pad_pin_map.items()] for mapping in sublist ]) bterms = mapping.block.getBTerms() print( set([bterm.getName() for bterm in bterms]) - set([ mapping[1] for sublist in [pair[1] for pair in pad_pin_map.items()] for mapping in sublist ])) assert pad_pins_to_label_count == len( bterms), "Some pins were not going to be labeled %d/%d" % ( pad_pins_to_label_count, len(bterms), ) print("Labeling", len(pad_pin_map), "pads") print("Labeling", pad_pins_to_label_count, "pad pins") if verbose: print(pad_pin_map) ############## labeled_count = 0 labeled = [] for inst in top.block.getInsts(): inst_name = inst.getName() if inst_name in pad_pin_map: for mapping in pad_pin_map[inst_name]: labeled_count += 1 pad_pin_name = mapping[0] pin_name = mapping[1] iotype = mapping[2] if verbose: print("Found: ", inst_name, pad_pin_name, pin_name) pad_iterm = inst.findITerm(pad_pin_name) labelITerm(top, pad_iterm, pin_name, iotype, all_shapes_flag=all_shapes) labeled.append(inst_name) assert labeled_count == pad_pins_to_label_count, ( "Didn't label what I set out to label %d/%d" % (labeled_count, pad_pins_to_label_count), set(pad_pin_map.keys()) - set(labeled), ) odb.write_def(top.block, output)
def contextualize( output, input_lef, top_def, top_lef, keep_inner_connections, input_def ): top = OdbReader(top_lef, top_def) macro = OdbReader([top_lef, input_lef], input_def) print("Block design name:", macro.name) print("Top-level design name:", top.name) nets_top = top.block.getNets() to_connect = {} MACRO_TOP_PLACEMENT_X = 0 MACRO_TOP_PLACEMENT_Y = 0 assert macro.name in [ inst.getMaster().getName() for inst in top.block.getInsts() ], "%s not found in %s" % (macro.name, top.name) for net in nets_top: iterms = net.getITerms() # asssumption: no pins (bterms) on top level block_net_name = None for iterm in iterms: macro_name = iterm.getMTerm().getMaster().getName() if macro_name == macro.name: block_net_name = iterm.getMTerm().getName() macro_top_inst = iterm.getInst() ( MACRO_TOP_PLACEMENT_X, MACRO_TOP_PLACEMENT_Y, ) = macro_top_inst.getLocation() print("Block net name: ", block_net_name) break if block_net_name is not None: to_connect[block_net_name] = [] for iterm in iterms: macro_name = iterm.getMTerm().getMaster().getName() if macro_name != macro.name: to_connect[block_net_name].append(iterm) block_net_name = None # print(macro_name, inst_name, end= ' ') # print(iterm.getMTerm().getName()) # print(to_connect) nets_macro = macro.block.getNets() created_macros = {} for net in nets_macro: iterms = net.getITerms() # asssumption: no pins (bterms) on top level if not keep_inner_connections: for iterm in iterms: iterm.disconnect() if net.getName() in to_connect: for node_iterm in to_connect[net.getName()]: node_master = node_iterm.getMTerm().getMaster() node_inst = node_iterm.getInst() node_inst_name = node_iterm.getInst().getName() if node_inst_name not in created_macros: created_macros[node_inst_name] = 1 print("Creating: ", node_master.getName(), node_inst_name) new_inst = odb.dbInst_create( macro.block, node_master, node_inst_name ) new_inst.setOrient(node_inst.getOrient()) new_inst.setLocation( node_inst.getLocation()[0] - MACRO_TOP_PLACEMENT_X, node_inst.getLocation()[1] - MACRO_TOP_PLACEMENT_Y, ) new_inst.setPlacementStatus("FIRM") else: new_inst = macro.block.findInst(node_inst_name) new_inst.findITerm(node_iterm.getMTerm().getName()).connect(net) odb.write_def(macro.block, output)
def io_place( config, ver_layer, hor_layer, ver_width_mult, hor_width_mult, length, hor_extension, ver_extension, reverse, bus_sort, output, input_lef, input_def, unmatched_error, ): """ 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 config_file_name = config bus_sort_flag = bus_sort unmatched_error_flag = unmatched_error 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 reader = OdbReader(lef_file_name, def_file_name) micron_in_units = reader.dbunits 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}") # 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 if len(line) > 1: print("Only one entry allowed per line.") sys.exit(1) 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", ]: 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 else: if len(token) == 3: token = token[0:2] reverse_arr.append(token) cur_side = token # build a list of pins H_LAYER = reader.tech.findLayer(h_layer_name) V_LAYER = reader.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:", reader.name) bterms = reader.block.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 = {} 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 unmatched_error_flag: print("Treating unmatched pins as errors. Exiting..") sys.exit(1) else: 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) assert len(reader.block.getBTerms()) == len(pin_placement["#N"] + pin_placement["#E"] + pin_placement["#S"] + pin_placement["#W"]) # generate slots DIE_AREA = reader.block.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 = reader.block.findTrackGrid(H_LAYER).getGridPatternY( 0) h_tracks = getGrid(origin, count, step) origin, count, step = reader.block.findTrackGrid(V_LAYER).getGridPatternX( 0) v_tracks = getGrid(origin, count, step) for rev in reverse_arr: pin_placement[rev].reverse() # create the pins 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()) print(f"Writing {output_def_file_name}...", ) odb.write_def(reader.block, output_def_file_name)
def write_powered_def( output, power_port, ground_port, powered_netlist, ignore_missing_pins, input_lef, input_def, ): """ Every cell having a pin labeled as a power pin (e.g., USE POWER) will be connected to the power/ground port of the design. """ def get_power_ground_ports(ports): vdd_ports = [] gnd_ports = [] for port in ports: if port.getSigType() == "POWER" or port.getName() == power_port: vdd_ports.append(port) elif port.getSigType() == "GROUND" or port.getName( ) == ground_port: gnd_ports.append(port) return (vdd_ports, gnd_ports) def find_power_ground_port(port_name, ports): for port in ports: if port.getName() == port_name: return port return None reader = OdbReader(input_lef, input_def) print(f"Top-level design name: {reader.name}") VDD_PORTS, GND_PORTS = get_power_ground_ports(reader.block.getBTerms()) assert ( VDD_PORTS and GND_PORTS ), "No power ports found at the top-level. Make sure that they exist and have the USE POWER|GROUND property or they match the arguments specified with --power-port and --ground-port." vdd_net_idx = None for index, port in enumerate(VDD_PORTS): if port.getNet().getName() == power_port: vdd_net_idx = index gnd_net_idx = None for index, port in enumerate(GND_PORTS): if port.getNet().getName() == ground_port: gnd_net_idx = index assert ( vdd_net_idx is not None ), "Can't find power net at the top-level. Make sure that argument specified with --power-port" assert ( gnd_net_idx is not None ), "Can't find ground net at the top-level. Make sure that argument specified with --ground-port" DEFAULT_VDD = VDD_PORTS[vdd_net_idx].getNet() DEFAULT_GND = GND_PORTS[gnd_net_idx].getNet() print("Default power net: ", DEFAULT_VDD.getName()) print("Default ground net:", DEFAULT_GND.getName()) print("Found a total of", len(VDD_PORTS), "power ports.") print("Found a total of", len(GND_PORTS), "ground ports.") modified_cells = 0 cells = reader.block.getInsts() for cell in cells: iterms = cell.getITerms() cell_name = cell.getName() if len(iterms) == 0: continue VDD_ITERMS = [] GND_ITERMS = [] VDD_ITERM_BY_NAME = None GND_ITERM_BY_NAME = None for iterm in iterms: if iterm.getSigType() == "POWER": VDD_ITERMS.append(iterm) elif iterm.getSigType() == "GROUND": GND_ITERMS.append(iterm) elif iterm.getMTerm().getName() == power_port: VDD_ITERM_BY_NAME = iterm elif iterm.getMTerm().getName() == ground_port: # note **PORT** GND_ITERM_BY_NAME = iterm if len(VDD_ITERMS) == 0: print( "Warning: No pins in the LEF view of", cell_name, " marked for use as power", ) print( "Warning: Attempting to match power pin by name (using top-level port name) for cell:", cell_name, ) if VDD_ITERM_BY_NAME is not None: # note **PORT** print("Found", power_port, "using that as a power pin") VDD_ITERMS.append(VDD_ITERM_BY_NAME) if len(GND_ITERMS) == 0: print( "Warning: No pins in the LEF view of", cell_name, " marked for use as ground", ) print( "Warning: Attempting to match ground pin by name (using top-level port name) for cell:", cell_name, ) if GND_ITERM_BY_NAME is not None: # note **PORT** print("Found", ground_port, "using that as a ground pin") GND_ITERMS.append(GND_ITERM_BY_NAME) if len(VDD_ITERMS) == 0 or len(GND_ITERMS) == 0: print("Warning: not all power pins found for cell:", cell_name) if ignore_missing_pins: print("Warning: ignoring", cell_name, "!!!!!!!") continue else: print( "Exiting... Use --ignore-missing-pins to ignore such errors" ) sys.exit(1) if len(VDD_ITERMS) > 2: print("Warning: cell", cell_name, "has", len(VDD_ITERMS), "power pins.") if len(GND_ITERMS) > 2: print("Warning: cell", cell_name, "has", len(GND_ITERMS), "ground pins.") for VDD_ITERM in VDD_ITERMS: if VDD_ITERM.isConnected(): pin_name = VDD_ITERM.getMTerm().getName() cell_name = cell_name print( "Warning: power pin", pin_name, "of", cell_name, "is already connected", ) print("Warning: ignoring", cell_name + "/" + pin_name, "!!!!!!!") else: VDD_ITERM.connect(DEFAULT_VDD) for GND_ITERM in GND_ITERMS: if GND_ITERM.isConnected(): pin_name = GND_ITERM.getMTerm().getName() cell_name = cell_name print( "Warning: ground pin", pin_name, "of", cell_name, "is already connected", ) print("Warning: ignoring", cell_name + "/" + pin_name, "!!!!!!!") else: GND_ITERM.connect(DEFAULT_GND) modified_cells += 1 print( "Modified power connections of", modified_cells, "cells (Remaining:", len(cells) - modified_cells, ").", ) # apply extra special connections taken from another netlist: if powered_netlist is not None and os.path.exists(powered_netlist): tmp_def_file = f"{os.path.splitext(powered_netlist)[0]}.def" openroad_script = [] openroad_script.append(f"read_lef {input_lef}") openroad_script.append(f"read_verilog {powered_netlist}") openroad_script.append(f"link_design {reader.name}") openroad_script.append(f"write_def {tmp_def_file}") openroad_script.append("exit") p = Popen(["openroad"], stdout=PIPE, stdin=PIPE, stderr=PIPE, encoding="utf8") openroad_script = "\n".join(openroad_script) openroad_stdout, openroad_stderr = p.communicate(openroad_script) print(f"STDOUT: {openroad_stdout.strip()}") print(f"STDERR: {openroad_stderr.strip()}") print("openroad exit code:", p.returncode) assert p.returncode == 0, p.returncode assert os.path.exists(tmp_def_file), "DEF file doesn't exist" power = OdbReader(input_lef, tmp_def_file) assert power.name == reader.name POWER_GROUND_PORT_NAMES = [ port.getName() for port in VDD_PORTS + GND_PORTS ] # using get_power_ground_ports doesn't work since the pins weren't # created using pdngen power_ground_ports = [ port for port in power.block.getBTerms() if port.getName() in POWER_GROUND_PORT_NAMES ] for port in power_ground_ports: iterms = port.getNet().getITerms() for iterm in iterms: inst_name = iterm.getInst().getName() pin_name = iterm.getMTerm().getName() original_inst = reader.block.findInst(inst_name) assert original_inst is not None, ( "Instance " + inst_name + " not found in the original netlist. Perhaps it was optimized out during synthesis?" ) original_iterm = original_inst.findITerm(pin_name) assert original_iterm is not None, ( inst_name + " doesn't have a pin named " + pin_name) original_port = find_power_ground_port(port.getName(), VDD_PORTS + GND_PORTS) assert original_port is not None, ( port.getName() + " not found in the original netlist.") original_iterm.connect(original_port.getNet()) print("Modified connections between", port.getName(), "and", inst_name) print(reader.block, output) odb.write_def(reader.block, output)
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")
def padringer( output, verilog_netlist, def_netlist, input_lef, width, height, padframe_config, pad_name_prefixes, init_only, working_dir, special_nets, design, ): """ Reads in a structural verilog containing pads and a LEF file that contains at least those pads and produces a DEF file with the padframe. TODO: core placement config init, external config """ config_file_name = padframe_config output_file_name = output lefs = [input_lef] working_def = f"{working_dir}/{design}.pf.def" working_cfg = f"{working_dir}/{design}.pf.cfg" for lef in lefs: assert os.path.exists(lef), lef + " doesn't exist" # hard requirement of a user netlist either as a DEF or verilog # this is to ensure that the padframe will contain all pads in the design # whether the config is autogenerated or user-provided assert (verilog_netlist is not None or def_netlist is not None ), "One of --verilog_netlist or --def-netlist is required" # Step 1: create an openDB database from the verilog/def using OpenSTA's read_verilog if verilog_netlist is not None: assert (def_netlist is None ), "Only one of --verilog_netlist or --def-netlist is required" assert design is not None, "--design is required" openroad_script = [] for lef in lefs: openroad_script.append(f"read_lef {lef}") openroad_script.append(f"read_verilog {verilog_netlist}") openroad_script.append(f"link_design {design}") openroad_script.append(f"write_def {working_def}") # openroad_script.append(f"write_db {design}.pf.db") openroad_script.append("exit") p = Popen(["openroad"], stdout=PIPE, stdin=PIPE, stderr=PIPE, encoding="utf8") openroad_script = "\n".join(openroad_script) # print(openroad_script) output = p.communicate(openroad_script) print("STDOUT:") print(output[0].strip()) print("STDERR:") print(output[1].strip()) print("openroad exit code:", p.returncode) assert p.returncode == 0, p.returncode # TODO: check for errors else: assert def_netlist is not None working_def = def_netlist assert os.path.exists(working_def), "DEF file doesn't exist" top = OdbReader(lefs, working_def) print(f"Top-level design name: {top.name}") ## Step 2: create a simple data structure with pads from the library # types: corner, power, other pads = {} libs = top.db.getLibs() for lib in libs: masters = lib.getMasters() for m in masters: name = m.getName() if m.isPad(): assert any(name.startswith(p) for p in pad_name_prefixes), name print("Found pad:", name) pad_type = m.getType() pads[name] = pad_type if pad_type == "PAD_SPACER": print("Found PAD_SPACER:", name) elif pad_type == "PAD_AREAIO": # using this for special bus fillers... print("Found PAD_AREAIO", name) if m.isEndCap(): # FIXME: regular endcaps assert any(name.startswith(p) for p in pad_name_prefixes), name assert not m.isPad(), name + " is both pad and endcap?" print("Found corner pad:", name) pads[name] = "corner" print() print("The I/O library contains", len(pads), "cells") print() assert len(pads) != 0, "^" ## Step 3: Go over instances in the design and extract the used pads used_pads = [] used_corner_pads = [] other_instances = [] for inst in top.block.getInsts(): inst_name = inst.getName() master_name = inst.getMaster().getName() if inst.isPad(): assert any(master_name.startswith(p) for p in pad_name_prefixes), master_name print("Found pad instance", inst_name, "of type", master_name) used_pads.append((inst_name, master_name)) elif inst.isEndCap(): # FIXME: regular endcaps assert any(master_name.startswith(p) for p in pad_name_prefixes), master_name print("Found pad instance", inst_name, "of type", master_name) print("Found corner pad instance", inst_name, "of type", master_name) used_corner_pads.append((inst_name, master_name)) else: assert not any( master_name.startswith(p) for p in pad_name_prefixes), master_name other_instances.append(inst_name) # FIXME: if used_corner_pads aren't supposed to be instantiated assert len(used_corner_pads) == 4, used_corner_pads print() print( "The user design contains", len(used_pads), "pads, 4 corner pads, and", len(other_instances), "other instances", ) print() assert len(used_pads) != 0, "^" ## Step 4: Generate a CFG or verify a user-provided config if config_file_name is not None: assert os.path.exists( config_file_name), config_file_name + " doesn't exist" with open(config_file_name, "r") as f: lines = f.readlines() user_config_pads = [] for line in lines: if line.startswith("CORNER") or line.startswith("PAD"): tokens = line.split() assert len(tokens) == 5, tokens inst_name, master_name = tokens[1], tokens[3] if (not pads[master_name] == "PAD_SPACER" and not pads[master_name] == "PAD_AREAIO"): user_config_pads.append((inst_name, master_name)) elif line.startswith("AREA"): tokens = line.split() assert len(tokens) == 4, tokens width = int(tokens[1]) height = int(tokens[2]) assert sorted(user_config_pads) == sorted( used_pads + used_corner_pads ), ( "Mismatch between the provided config and the provided netlist. Diff:", diff_lists(user_config_pads, used_pads + used_corner_pads), ) print("User config verified") working_cfg = config_file_name else: # TODO: get minimum width/height so that --width and --height aren't required assert width is not None, "--width is required" assert height is not None, "--height is required" # auto generate a configuration # TODO: after calssification, center power pads on each side north, east, south, west = chunker(used_pads, 4) with open(working_cfg, "w") as f: f.write( generate_cfg(north, east, south, west, used_corner_pads, width, height)) if not init_only: invoke_padring(working_cfg, working_def, lefs) else: print( "Padframe config generated at", working_cfg, f"Modify it and re-run this program with the '-cfg {working_cfg}' option", ) sys.exit() print("Applying pad placements to the design DEF") padframe = OdbReader(lefs, working_def) assert padframe.name == "PADRING", padframe.name print("Padframe design name:", padframe.name) # Mark special nets if special_nets is not None: for net in top.block.getNets(): net_name = net.getName() if net_name in special_nets: print("Marking", net_name, "as a special net") net.setSpecial() for iterm in net.getITerms(): iterm.setSpecial() # get minimum width/height (core-bounded) placed_cells_count = 0 created_cells_count = 0 for inst in padframe.block.getInsts(): assert inst.isPad() or inst.isEndCap(), ( inst.getName() + " is neither a pad nor corner pad") inst_name = inst.getName() master = inst.getMaster() master_name = master.getName() x, y = inst.getLocation() orient = inst.getOrient() if (inst_name, master_name) in used_pads + used_corner_pads: original_inst = top.block.findInst(inst_name) assert original_inst is not None, "Failed to find " + inst_name assert original_inst.getPlacementStatus() == "NONE", ( inst_name + " is already placed") original_inst.setOrient(orient) original_inst.setLocation(x, y) original_inst.setPlacementStatus("FIRM") placed_cells_count += 1 else: # must be a filler cell new_inst = odb.dbInst_create(top.block, top.db.findMaster(master_name), inst_name) assert new_inst is not None, "Failed to create " + inst_name new_inst.setOrient(orient) new_inst.setLocation(x, y) new_inst.setPlacementStatus("FIRM") created_cells_count += 1 # TODO: place the core macros within the padframe (chip floorplan) for inst in top.block.getInsts(): if inst.isPlaced() or inst.isFixed(): continue print("Placing", inst.getName()) master = inst.getMaster() master_width = master.getWidth() master_height = master.getHeight() print(master_width, master_height) print(width, height) inst.setLocation( width * 1000 // 2 - master_width // 2, height * 1000 // 2 - master_height // 2, ) inst.setPlacementStatus("PLACED") odb.write_def(top.block, output_file_name) print("Done.")