diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 31802ee7..bdb7258b 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -1913,6 +1913,42 @@ class layout(): # Just use the power pin function for now to save code self.add_power_pin(new_name, pin.center(), start_layer=start_layer, directions=directions) + def add_power_pin_no_via(self, name, loc, directions=None, start_layer="m1"): + # same function like normal one, but do not add via to the gird layer + # Hack for min area + if OPTS.tech_name == "sky130": + min_area = drc["minarea_{}".format(self.pwr_grid_layers[1])] + width = round_to_grid(sqrt(min_area)) + height = round_to_grid(min_area / width) + else: + width = None + height = None + + pin = None + if start_layer in self.pwr_grid_layers: + pin = self.add_layout_pin_rect_center(text=name, + layer=start_layer, + offset=loc, + width=width, + height=height) + else: + via = self.add_via_stack_center(from_layer=start_layer, + to_layer=start_layer,# so only enclosure shape will be added + offset=loc, + directions=directions) + + if not width: + width = via.width + if not height: + height = via.height + pin = self.add_layout_pin_rect_center(text=name, + layer=start_layer, + offset=loc, + width=width, + height=height) + + return pin + def add_power_pin(self, name, loc, directions=None, start_layer="m1"): # Hack for min area if OPTS.tech_name == "sky130": @@ -2027,7 +2063,7 @@ class layout(): layer=layer, offset=peri_pin_loc) - def add_dnwell(self, bbox=None, inflate=1): + def add_dnwell(self, bbox=None, inflate=1, add_vias=True): """ Create a dnwell, along with nwell moat at border. """ if "dnwell" not in tech_layer: @@ -2049,11 +2085,20 @@ class layout(): ul = vector(ll.x, ur.y) lr = vector(ur.x, ll.y) - # Add the dnwell - self.add_rect("dnwell", - offset=ll, - height=ur.y - ll.y, - width=ur.x - ll.x) + # Hack for sky130 klayout drc rule nwell.6 + if OPTS.tech_name == "sky130": + # Apply the drc rule + # Add the dnwell + self.add_rect("dnwell", + offset=ll - vector(0.5 * self.nwell_width, 0.5 * self.nwell_width) - vector(drc["minclosure_nwell_by_dnwell"], drc["minclosure_nwell_by_dnwell"]), + height=ur.y - ll.y + self.nwell_width + 2 * drc["minclosure_nwell_by_dnwell"], + width=ur.x - ll.x + self.nwell_width + 2 * drc["minclosure_nwell_by_dnwell"]) + else: # other tech + # Add the dnwell + self.add_rect("dnwell", + offset=ll, + height=ur.y - ll.y, + width=ur.x - ll.x) # Add the moat self.add_path("nwell", [ll, lr, ur, ul, ll - vector(0, 0.5 * self.nwell_width)]) @@ -2080,9 +2125,14 @@ class layout(): to_layer="m1", offset=loc) else: - self.add_power_pin(name="vdd", - loc=loc, - start_layer="li") + if add_vias: + self.add_power_pin(name="vdd", + loc=loc, + start_layer="li") + else: + self.add_power_pin_no_via(name="vdd", + loc=loc, + start_layer="li") count += 1 loc += nwell_offset.scale(tap_spacing, 0) @@ -2100,9 +2150,14 @@ class layout(): to_layer="m1", offset=loc) else: - self.add_power_pin(name="vdd", - loc=loc, - start_layer="li") + if add_vias: + self.add_power_pin(name="vdd", + loc=loc, + start_layer="li") + else: + self.add_power_pin_no_via(name="vdd", + loc=loc, + start_layer="li") count += 1 loc += nwell_offset.scale(tap_spacing, 0) @@ -2120,9 +2175,14 @@ class layout(): to_layer="m2", offset=loc) else: - self.add_power_pin(name="vdd", - loc=loc, - start_layer="li") + if add_vias: + self.add_power_pin(name="vdd", + loc=loc, + start_layer="li") + else: + self.add_power_pin_no_via(name="vdd", + loc=loc, + start_layer="li") count += 1 loc += nwell_offset.scale(0, tap_spacing) @@ -2140,9 +2200,14 @@ class layout(): to_layer="m2", offset=loc) else: - self.add_power_pin(name="vdd", - loc=loc, - start_layer="li") + if add_vias: + self.add_power_pin(name="vdd", + loc=loc, + start_layer="li") + else: + self.add_power_pin_no_via(name="vdd", + loc=loc, + start_layer="li") count += 1 loc += nwell_offset.scale(0, tap_spacing) diff --git a/compiler/modules/sram_1bank.py b/compiler/modules/sram_1bank.py index 3fada4d5..88349dcf 100644 --- a/compiler/modules/sram_1bank.py +++ b/compiler/modules/sram_1bank.py @@ -53,6 +53,9 @@ class sram_1bank(design, verilog, lef): # delay control logic does not have RBLs self.has_rbl = OPTS.control_logic != "control_logic_delay" + # IO pins, except power, list of pin names + self.pins_to_route = [] + def add_pins(self): """ Add pins for entire SRAM. """ @@ -244,6 +247,31 @@ class sram_1bank(design, verilog, lef): def create_modules(self): debug.error("Must override pure virtual function.", -1) + def route_supplies_constructive(self, bbox=None): + # prepare the "router" + from openram.router.supply_placer import supply_placer as router + rtr = router(layers=self.supply_stack, + design=self, + bbox=bbox, + pin_type=OPTS.supply_pin_type, + ext_vdd_name=self.vdd_name, + ext_gnd_name=self.gnd_name) + # add power rings / side pins + if OPTS.supply_pin_type in ["top", "bottom", "right", "left"]: + rtr.add_side_pin(self.vdd_name) + rtr.add_side_pin(self.gnd_name) + elif OPTS.supply_pin_type == "ring": + rtr.add_ring_pin(self.vdd_name)# ring vdd name + rtr.add_ring_pin(self.gnd_name) + else: + debug.warning("Side supply pins aren't created.") + + # maze router the bank power pins + for pin_name in ["vdd", "gnd"]: + for inst in self.bank_insts: + self.copy_power_pins(inst, pin_name) + rtr.route_bank() + def route_supplies(self, bbox=None): """ Route the supply grid and connect the pins to them. """ @@ -324,44 +352,41 @@ class sram_1bank(design, verilog, lef): """ Add the top-level pins for a single bank SRAM with control. """ - - # List of pin to new pin name - pins_to_route = [] for port in self.all_ports: # Connect the control pins as inputs for signal in self.control_logic_inputs[port]: if signal.startswith("rbl"): continue if signal=="clk": - pins_to_route.append("{0}{1}".format(signal, port)) + self.pins_to_route.append("{0}{1}".format(signal, port)) else: - pins_to_route.append("{0}{1}".format(signal, port)) + self.pins_to_route.append("{0}{1}".format(signal, port)) if port in self.write_ports: for bit in range(self.word_size + self.num_spare_cols): - pins_to_route.append("din{0}[{1}]".format(port, bit)) + self.pins_to_route.append("din{0}[{1}]".format(port, bit)) if port in self.readwrite_ports or port in self.read_ports: for bit in range(self.word_size + self.num_spare_cols): - pins_to_route.append("dout{0}[{1}]".format(port, bit)) + self.pins_to_route.append("dout{0}[{1}]".format(port, bit)) for bit in range(self.col_addr_size): - pins_to_route.append("addr{0}[{1}]".format(port, bit)) + self.pins_to_route.append("addr{0}[{1}]".format(port, bit)) for bit in range(self.row_addr_size): - pins_to_route.append("addr{0}[{1}]".format(port, bit + self.col_addr_size)) + self.pins_to_route.append("addr{0}[{1}]".format(port, bit + self.col_addr_size)) if port in self.write_ports: if self.write_size != self.word_size: for bit in range(self.num_wmasks): - pins_to_route.append("wmask{0}[{1}]".format(port, bit)) + self.pins_to_route.append("wmask{0}[{1}]".format(port, bit)) if port in self.write_ports: if self.num_spare_cols == 1: - pins_to_route.append("spare_wen{0}".format(port)) + self.pins_to_route.append("spare_wen{0}".format(port)) else: for bit in range(self.num_spare_cols): - pins_to_route.append("spare_wen{0}[{1}]".format(port, bit)) + self.pins_to_route.append("spare_wen{0}[{1}]".format(port, bit)) if route_option == "classic": from openram.router import signal_escape_router as router @@ -373,7 +398,7 @@ class sram_1bank(design, verilog, lef): bbox=bbox, design=self, mod=mod) - rtr.route(pins_to_route) + rtr.route(self.pins_to_route) elif route_option == "fast": # use io_pin_placer # put the IO pins at the edge @@ -381,10 +406,10 @@ class sram_1bank(design, verilog, lef): pl = placer(layers=self.m3_stack, bbox=bbox, design=self) - for name in pins_to_route: + for name in self.pins_to_route: debug.warning("pins_to_route pins -> {0}".format(name)) - pl.add_io_pins_connected(pins_to_route) - #pl.add_io_pins(pins_to_route) + pl.add_io_pins_connected(self.pins_to_route) + #pl.add_io_pins(self.pins_to_route) def compute_bus_sizes(self): """ Compute the independent bus widths shared between two and four bank SRAMs """ @@ -1089,7 +1114,7 @@ class sram_1bank(design, verilog, lef): self.add_layout_pins() # Some technologies have an isolation - self.add_dnwell(inflate=2.5) + self.add_dnwell(inflate=2.5, add_vias=False) init_bbox = self.get_bbox() # Route the supplies together and/or to the ring/stripes. @@ -1100,7 +1125,8 @@ class sram_1bank(design, verilog, lef): self.route_escape_pins(bbox=init_bbox, mod=mod, route_option=route_option) if OPTS.route_supplies: - self.route_supplies(init_bbox) + #self.route_supplies(init_bbox) + self.route_supplies_constructive(init_bbox) def route_dffs(self, add_routes=True): diff --git a/compiler/router/io_pin_placer.py b/compiler/router/io_pin_placer.py index f3a416ab..27125dc3 100644 --- a/compiler/router/io_pin_placer.py +++ b/compiler/router/io_pin_placer.py @@ -412,7 +412,7 @@ class io_pin_placer(router): return [source_pin.uc(), target_pin.bc()] else: # need intermediate point - via_basic_y = ll.y + 21 # 21 is magic number, make sure out of dff area + via_basic_y = ll.y + 22 # 22 is magic number, make sure out of dff area if is_up: via_basic_y = via_basic_y + 0.5 else: diff --git a/compiler/router/router.py b/compiler/router/router.py index 22169cc8..4b58a51e 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -111,6 +111,54 @@ class router(router_tech): self.all_pins.update(pin_set) + def find_pins_inside(self, pin_name): + # find pins except moat, power ring, the moat pins will be store as list and return + """ Find the pins with the given name. """ + debug.info(4, "Finding all pins for {}".format(pin_name)) + moat_pins = [] + shape_list = self.layout.getAllPinShapes(str(pin_name)) + pin_set = set() + for shape in shape_list: + layer, boundary = shape + # gdsMill boundaries are in (left, bottom, right, top) order + ll = vector(boundary[0], boundary[1]) + ur = vector(boundary[2], boundary[3]) + rect = [ll, ur] + new_pin = graph_shape(pin_name, rect, layer) + # Skip this pin if it's contained by another pin of the same type + if new_pin.core_contained_by_any(pin_set): + continue + # skip the moat pin + if self.check_pin_on_moat(new_pin): + moat_pins.append(new_pin) + continue + # Merge previous pins into this one if possible + self.merge_shapes(new_pin, pin_set) + pin_set.add(new_pin) + # Add these pins to the 'pins' dict + self.pins[pin_name] = pin_set + self.all_pins.update(pin_set) + return moat_pins + + + def check_pin_on_moat(self, pin_shape): + """ Check if a given pin is on the moat. """ + ll, ur = self.bbox + left_x = ll.x + right_x = ur.x + bottom_y = ll.y + top_y = ur.y + + threshold = 10 # inside this distance, could be considered as on the moat + + is_on_left = abs(pin_shape.center().x - left_x) < threshold + is_on_right = abs(pin_shape.center().x - right_x) < threshold + is_on_bottom = abs(pin_shape.center().y - bottom_y) < threshold + is_on_top = abs(pin_shape.center().y - top_y) < threshold + + return is_on_left or is_on_right or is_on_bottom or is_on_top + + def find_blockages(self, name="blockage", shape_list=None): """ Find all blockages in the routing layers. """ debug.info(4, "Finding blockages...") diff --git a/compiler/router/supply_placer.py b/compiler/router/supply_placer.py new file mode 100644 index 00000000..bec2b07c --- /dev/null +++ b/compiler/router/supply_placer.py @@ -0,0 +1,344 @@ +# See LICENSE for licensing information. +# +# Copyright (c) 2016-2024 Regents of the University of California, Santa Cruz +# All rights reserved. +# +from openram import debug +from openram.base.vector import vector +from openram import OPTS +from .graph import graph +from .graph_shape import graph_shape +from .router import router + +class supply_placer(router): + + def __init__(self, layers, design, bbox=None, pin_type=None, ext_vdd_name="vccd1", ext_gnd_name="vssd1"): + + # `router` is the base router class + router.__init__(self, layers, design, bbox) + + # Side supply pin type + # (can be "top", "bottom", "right", "left", and "ring") + self.pin_type = pin_type + # New pins are the side supply pins + self.new_pins = {} + # external power name of the whole macro + self.ext_vdd_name = ext_vdd_name + self.ext_gnd_name = ext_gnd_name + # instances + self.insts = self.design.insts + # moat pins + self.moat_pins = [] + # io pins + self.io_pins_left = [] + self.io_pins_right = [] + self.io_pins_top = [] + self.io_pins_bottom = [] + + def route_bank(self, vdd_name="vdd", gnd_name="gnd"): + debug.info(1, "Running router for {} and {}...".format(vdd_name, gnd_name)) + + # Save pin names + self.vdd_name = vdd_name + self.gnd_name = gnd_name + + # Prepare gdsMill to find pins and blockages + self.prepare_gds_reader() + + # Find vdd/gnd pins of bank, to be routed + self.moat_pins = self.find_pins_inside(vdd_name) + self.find_pins_inside(gnd_name) + + # Find blockages and vias + self.find_blockages() + self.find_vias() + + # Convert blockages and vias if they overlap a pin + self.convert_vias() + self.convert_blockages() + + # Add vdd and gnd pins as blockages as well + # NOTE: This is done to make vdd and gnd pins DRC-safe + for pin in self.all_pins: + self.blockages.append(self.inflate_shape(pin)) + + # Route vdd and gnd + routed_count = 0 + routed_max = len(self.pins[vdd_name]) + len(self.pins[gnd_name]) + for pin_name in [vdd_name, gnd_name]: + pins = self.pins[pin_name] + # Route closest pins according to the minimum spanning tree + for source, target in self.get_mst_pairs(list(pins)): + # Create the graph + g = graph(self) + g.create_graph(source, target) + # Find the shortest path from source to target + path = g.find_shortest_path() + # If no path is found, throw an error + if path is None: + self.write_debug_gds(gds_name="{}error.gds".format(OPTS.openram_temp), g=g, source=source, target=target) + debug.error("Couldn't route from {} to {}.".format(source, target), -1) + # Create the path shapes on layout + new_wires, new_vias = self.add_path(path) + # Find the recently added shapes + self.find_blockages(pin_name, new_wires) + self.find_vias(new_vias) + # Report routed count + routed_count += 1 + debug.info(2, "Routed {} of {} supply pins".format(routed_count, routed_max)) + + + #def route_moat(self): + + #def route_other(self): + + def check_overlap(self, moat_pin, io_pin_names): + # use all the IO pins(at correspoding edge) to check overlap, check 1 moat vdd pin, give the corresponding target/source position as list, and pull the source up to m3 + add_distance = 0 + direction = 1 + self.prepare_io_pins(io_pin_names) + # judge the edge of moat vdd + edge = self.get_closest_edge(moat_pin) + source_center = moat_pin.center() + if edge == "bottom": + pin_too_close = any(abs(io_pin.center().x - source_center.x) < self.track_width for io_pin in self.io_pins_bottom) + tmp_center = source_center + while pin_too_close: + tmp_center = source_center + add_distance = add_distance + 0.1 + if direction == 1: # right shift + tmp_center = tmp_center + add_distance + else: # left shift + tmp_center = tmp_center - add_distance + pin_too_close = any(abs(io_pin.center().x - tmp_center.x) < self.track_width for io_pin in self.io_pins_bottom) + if tmp_center == source_center: # no overlap + # no jog, direct pull to m3 + self.design.copy_power_pin(moat_pin, loc=None, directions=None, new_name="") + else: # need jog + # shift the center + # add rectangle at same layer (original) + self.design.add + elif edge == "top": + pass + elif edge == "left": + pass + else: #right + pass + + def prepare_io_pins(self, io_pin_names): + # io_pin_names is a list + # find all the io pins + for pin_name in io_pin_names: + self.find_pins(pin_name)# pin now in self.pins + io_pin = self.pins[pin_name] + self.find_closest_edge(io_pin) + + + def get_closest_edge(self, pin): + """ Return a point's the closest edge and the edge's axis direction. Here we use to find the edge of moat vdd """ + + ll, ur = self.bbox + point = pin.center() + # Snap the pin to the perimeter and break the iteration + ll_diff_x = abs(point.x - ll.x) + ll_diff_y = abs(point.y - ll.y) + ur_diff_x = abs(point.x - ur.x) + ur_diff_y = abs(point.y - ur.y) + min_diff = min(ll_diff_x, ll_diff_y, ur_diff_x, ur_diff_y) + + if min_diff == ll_diff_x: + return "left" + if min_diff == ll_diff_y: + return "bottom" + if min_diff == ur_diff_x: + return "right" + return "top" + + + def find_closest_edge(self, pin): + """ Use to find the edge, where the io pin locats """ + + ll, ur = self.bbox + point = pin.center() + # Snap the pin to the perimeter and break the iteration + ll_diff_x = abs(point.x - ll.x) + ll_diff_y = abs(point.y - ll.y) + ur_diff_x = abs(point.x - ur.x) + ur_diff_y = abs(point.y - ur.y) + min_diff = min(ll_diff_x, ll_diff_y, ur_diff_x, ur_diff_y) + + if min_diff == ll_diff_x: + self.io_pins_left.append(pin) + elif min_diff == ll_diff_y: + self.io_pins_bottom.append(pin) + elif min_diff == ur_diff_x: + self.io_pins_right.append(pin) + else: + self.io_pins_top.append(pin) + + + def add_side_pin(self, pin_name, side, num_vias=3, num_fake_pins=4): + """ Add supply pin to one side of the layout. """ + + ll, ur = self.bbox + vertical = side in ["left", "right"] + inner = pin_name == self.ext_vdd_name + + # Calculate wires' wideness + wideness = self.track_wire * num_vias + self.track_space * (num_vias - 1) + + # Calculate the offset for the inner ring + if inner: + margin = wideness * 2 + else: + margin = 0 + + # Calculate the lower left coordinate + if side == "top": + offset = vector(ll.x + margin, ur.y - wideness - margin) + elif side == "bottom": + offset = vector(ll.x + margin, ll.y + margin) + elif side == "left": + offset = vector(ll.x + margin, ll.y + margin) + elif side == "right": + offset = vector(ur.x - wideness - margin, ll.y + margin) + + # Calculate width and height + shape = ur - ll + if vertical: + shape_width = wideness + shape_height = shape.y + else: + shape_width = shape.x + shape_height = wideness + if inner: + if vertical: + shape_height -= margin * 2 + else: + shape_width -= margin * 2 + + # Add this new pin + layer = self.get_layer(int(vertical)) + pin = self.design.add_layout_pin(text=pin_name, + layer=layer, + offset=offset, + width=shape_width, + height=shape_height) + + # Add fake pins on this new pin evenly + fake_pins = [] + if vertical: + space = (shape_height - (2 * wideness) - num_fake_pins * self.track_wire) / (num_fake_pins + 1) + start_offset = vector(offset.x, offset.y + wideness) + else: + space = (shape_width - (2 * wideness) - num_fake_pins * self.track_wire) / (num_fake_pins + 1) + start_offset = vector(offset.x + wideness, offset.y) + for i in range(1, num_fake_pins + 1): + if vertical: + offset = vector(start_offset.x, start_offset.y + i * (space + self.track_wire)) + ll = vector(offset.x, offset.y - self.track_wire) + ur = vector(offset.x + wideness, offset.y) + else: + offset = vector(start_offset.x + i * (space + self.track_wire), start_offset.y) + ll = vector(offset.x - self.track_wire, offset.y) + ur = vector(offset.x, offset.y + wideness) + rect = [ll, ur] + fake_pin = graph_shape(name=pin_name, + rect=rect, + layer_name_pp=layer) + fake_pins.append(fake_pin) + return pin, fake_pins + + + def add_ring_pin(self, pin_name, num_vias=3, num_fake_pins=4): + """ Add the supply ring to the layout. """ + + # Add side pins + new_pins = [] + for side in ["top", "bottom", "right", "left"]: + new_shape, fake_pins = self.add_side_pin(pin_name, side, num_vias, num_fake_pins) + ll, ur = new_shape.rect + rect = [ll, ur] + layer = self.get_layer(side in ["left", "right"]) + new_pin = graph_shape(name=pin_name, + rect=rect, + layer_name_pp=layer) + new_pins.append(new_pin) + #self.pins[pin_name].update(fake_pins) + self.fake_pins.extend(fake_pins) + + # Add vias to the corners + shift = self.track_wire + self.track_space + half_wide = self.track_wire / 2 + for i in range(4): + ll, ur = new_pins[i].rect + if i % 2: + top_left = vector(ur.x - (num_vias - 1) * shift - half_wide, ll.y + (num_vias - 1) * shift + half_wide) + else: + top_left = vector(ll.x + half_wide, ur.y - half_wide) + for j in range(num_vias): + for k in range(num_vias): + offset = vector(top_left.x + j * shift, top_left.y - k * shift) + self.design.add_via_center(layers=self.layers, + offset=offset) + + # Save side pins for routing + self.new_pins[pin_name] = new_pins + for pin in new_pins: + self.blockages.append(self.inflate_shape(pin)) + + + def get_mst_pairs(self, pins): + """ + Return the pin pairs from the minimum spanning tree in a graph that + connects all pins together. + """ + + pin_count = len(pins) + + # Create an adjacency matrix that connects all pins + edges = [[0] * pin_count for i in range(pin_count)] + for i in range(pin_count): + for j in range(pin_count): + # Skip if they're the same pin + if i == j: + continue + # Skip if both pins are fake + if pins[i] in self.fake_pins and pins[j] in self.fake_pins: + continue + edges[i][j] = pins[i].distance(pins[j]) + + pin_connected = [False] * pin_count + pin_connected[0] = True + + # Add the minimum cost edge in each iteration (Prim's) + mst_pairs = [] + for i in range(pin_count - 1): + min_cost = float("inf") + s = 0 + t = 0 + # Iterate over already connected pins + for m in range(pin_count): + # Skip if not connected + if not pin_connected[m]: + continue + # Iterate over this pin's neighbors + for n in range(pin_count): + # Skip if already connected or isn't a neighbor + if pin_connected[n] or edges[m][n] == 0: + continue + # Choose this edge if it's better the the current one + if edges[m][n] < min_cost: + min_cost = edges[m][n] + s = m + t = n + pin_connected[t] = True + mst_pairs.append((pins[s], pins[t])) + + return mst_pairs + + + def get_new_pins(self, name): + """ Return the new supply pins added by this router. """ + + return self.new_pins[name] \ No newline at end of file diff --git a/compiler/router/supply_router.py b/compiler/router/supply_router.py index fc61f4e6..3e1f431e 100644 --- a/compiler/router/supply_router.py +++ b/compiler/router/supply_router.py @@ -97,7 +97,7 @@ class supply_router(router): ll, ur = self.bbox vertical = side in ["left", "right"] - inner = pin_name == self.gnd_name + inner = pin_name == self.vdd_name # Calculate wires' wideness wideness = self.track_wire * num_vias + self.track_space * (num_vias - 1) diff --git a/sram_compiler.py b/sram_compiler.py index 9d5fc4b3..e0727ea3 100755 --- a/sram_compiler.py +++ b/sram_compiler.py @@ -70,7 +70,7 @@ for path in output_files: # Create an SRAM (we can also pass sram_config, see documentation/tutorials for details) from openram import sram -s = sram(route_option="classic")# "classic" or "fast" +s = sram(route_option="fast")# "classic" or "fast" # Output the files for the resulting SRAM s.save()