diff --git a/compiler/base/channel_route.py b/compiler/base/channel_route.py index 5eb4da63f..515cce788 100644 --- a/compiler/base/channel_route.py +++ b/compiler/base/channel_route.py @@ -5,12 +5,15 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # +# This version aims to keep the track out/vertical to the dff pins +# Now only consider the channel at the south, but works fine with channel at the north import collections from openram import debug from openram.tech import drc from .vector import vector from .design import design - +from openram import OPTS +import re class channel_net(): def __init__(self, net_name, pins, vertical): @@ -85,7 +88,8 @@ def __init__(self, layer_stack, directions=None, vertical=False, - parent=None): + parent=None, + dff_area=False): """ The net list is a list of the nets with each net being a list of pins to be connected. The offset is the lower-left of where the @@ -106,6 +110,7 @@ def __init__(self, self.vertical = vertical # For debugging... self.parent = parent + self.dff_area = dff_area # this is a special value to handle dff areas, should be true when routing col_dff/dffs if not directions or directions == "pref": # Use the preferred layer directions @@ -139,6 +144,16 @@ def __init__(self, layer_stuff = self.get_layer_pitch(self.horizontal_layer) (self.horizontal_nonpref_pitch, self.horizontal_pitch, self.horizontal_width, self.horizontal_space) = layer_stuff + # For debug + + debug.warning("layer horizontal: {0}".format(self.horizontal_layer)) + debug.warning("horizontal_nonpref_pitch: {0}".format(self.horizontal_nonpref_pitch)) + debug.warning("horizontal_pitch: {0}".format(self.horizontal_pitch)) + debug.warning("horizontal_space: {0}".format(self.horizontal_space)) + debug.warning("layer vertical: {0}".format(self.vertical_layer)) + debug.warning("vertiacl_nonpref_pitch: {0}".format(self.vertical_pitch)) + debug.warning("vertical_pitch: {0}".format(self.vertical_pitch)) + debug.warning("vertical_space: {0}".format(self.vertical_space)) self.route() @@ -220,7 +235,9 @@ def route(self): else: real_channel_offset = vector(min(self.min_value, self.offset.x), self.offset.y) current_offset = real_channel_offset - + if self.dff_area == True: + if self.layer_stack == self.m2_stack: + self.vertical_nonpref_pitch = self.horizontal_pitch + 0.1 # 0.1 make sure even if via at same col, fulfill m3-m3 spacing # Sort nets by left edge value nets.sort() while len(nets) > 0: @@ -245,9 +262,14 @@ def route(self): self.horizontal_pitch) current_offset = vector(current_offset.x, net.max_value + self.horizontal_nonpref_pitch) else: - self.add_horizontal_trunk_route(net.pins, - current_offset, - self.vertical_pitch) + if self.dff_area == True: # only use in dff channel routing + self.add_horizontal_trunk_with_jog(net.pins, + current_offset, + self.vertical_pitch) + else: + self.add_horizontal_trunk_route(net.pins, + current_offset, + self.vertical_pitch) current_offset = vector(net.max_value + self.vertical_nonpref_pitch, current_offset.y) # Remove the net from other constriants in the VCG @@ -297,6 +319,265 @@ def get_layer_pitch(self, layer): debug.error("Cannot find layer pitch.", -1) return (nonpref_pitch, pitch, pitch - space, space) + def check_need_jog(self, pin_name): + match = re.search(r'^din\d+_(\d+)$', pin_name) + if match: + number = int(match.group(1)) + if number % int(OPTS.write_size) == 0: + return True + else: + return False + else: + return False + + def add_horizontal_trunk_with_jog(self, + pins, + trunk_offset, + pitch): + """ Special for connecting channel of dffs & bank, avoid too close with vdd pins """ + max_x = max([pin.center().x for pin in pins]) + min_x = min([pin.center().x for pin in pins]) + min_y = min([pin.center().y for pin in pins]) + max_y = max([pin.center().y for pin in pins]) + # see the channel is at top or bottom + if min_y < 0: # port0 + for pin in pins: + if pin.center().x == max_x: + if pin.center().y == max_y: + min_x = min_x - 0.1 # in order to add jog at the dff pins, avoid overlap with vdd pins, left shift vertical line at dout pin + else: # pin has max_x is under track + max_x = max_x - 0.1 + port = 0 + else: # port1 + for pin in pins: + if pin.center().x == max_x: + if pin.center().y == max_y: + max_x = max_x - 0.1 # in order to add jog at the dff pins, avoid overlap with vdd pins, left shift vertical line at dout pin + else: # pin has max_x is under track + min_x = min_x - 0.1 + port = 1 + # if we are less than a pitch, just create a non-preferred layer jog + non_preferred_route = max_x - min_x <= pitch + half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)] + if port == 0: # bottom need shift + if non_preferred_route: + # Add the horizontal trunk on the vertical layer! + self.add_path(self.vertical_layer, + [vector(min_x - half_layer_width, trunk_offset.y), + vector(max_x + half_layer_width, trunk_offset.y)]) + + # Route each pin to the trunk + for pin in pins: + if pin.cy() < trunk_offset.y: + pin_pos = pin.center() + mid = vector(pin_pos.x - 0.1, trunk_offset.y) + self.add_path(self.vertical_layer, [vector(pin.bc().x - 0.1, pin.bc().y), mid]) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin_pos) + else: + if self.check_need_jog(pin.name) and (OPTS.write_size != OPTS.word_size):# for port0, only pin above track needs to check + # right first + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin.rc()) + point_1 = vector(pin.center().x + pitch, pin.center().y) + self.add_path(self.vertical_layer, [pin.rc(), point_1]) + # straight out of bank area + self.add_via_stack_center(from_layer=self.vertical_layer, + to_layer=self.vertical_layer, + offset=point_1) + point_2 = vector(point_1.x, point_1.y - 5.6) + self.add_path(self.vertical_layer, [point_1, point_2]) + # then left + self.add_via_stack_center(from_layer=self.vertical_layer, + to_layer=self.vertical_layer, + offset=point_2) + point_3 = vector(pin.center().x, pin.center().y - 5.6) + self.add_path(self.vertical_layer, [point_2, point_3]) + # back to normal + self.add_via_stack_center(from_layer=self.vertical_layer, + to_layer=self.vertical_layer, + offset=point_3) + mid = vector(point_3.x, trunk_offset.y) + self.add_path(self.vertical_layer, [point_3, mid]) + else: # do not need to care of wmask + pin_pos = pin.bc() + # No bend needed here + mid = vector(pin_pos.x, trunk_offset.y) + self.add_path(self.vertical_layer, [pin_pos, mid]) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin.bc()) + else: + # Add the horizontal trunk + self.add_path(self.horizontal_layer, + [vector(min_x, trunk_offset.y), + vector(max_x, trunk_offset.y)]) + + # Route each pin to the trunk + for pin in pins: + debug.warning("pin name in net ----> {0}".format(pin.name)) + debug.warning("wmask or not --> {0}".format(OPTS.write_size)) + # Find the correct side of the pin + if pin.cy() < trunk_offset.y: + pin_pos = pin.center() + mid = vector(pin_pos.x - 0.1, trunk_offset.y) + self.add_path(self.vertical_layer, [vector(pin.bc().x - 0.1, pin.bc().y), mid]) + self.add_via_center(layers=self.layer_stack, + offset=mid, + directions=self.directions) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin_pos) + else: + if self.check_need_jog(pin.name) and (OPTS.write_size != OPTS.word_size):# for port0, only pin above track needs to check + # right first + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin.rc()) + point_1 = vector(pin.center().x + pitch, pin.center().y) + self.add_path(self.vertical_layer, [pin.rc(), point_1]) + # straight out of bank area + self.add_via_stack_center(from_layer=self.vertical_layer, + to_layer=self.vertical_layer, + offset=point_1) + point_2 = vector(point_1.x, point_1.y - 5.6) + self.add_path(self.vertical_layer, [point_1, point_2]) + # then left + self.add_via_stack_center(from_layer=self.vertical_layer, + to_layer=self.vertical_layer, + offset=point_2) + point_3 = vector(pin.center().x, pin.center().y - 5.6) + self.add_path(self.vertical_layer, [point_2, point_3]) + # back to normal + self.add_via_stack_center(from_layer=self.vertical_layer, + to_layer=self.vertical_layer, + offset=point_3) + mid = vector(point_3.x, trunk_offset.y) + self.add_path(self.vertical_layer, [point_3, mid]) + self.add_via_center(layers=self.layer_stack, + offset=mid, + directions=self.directions) + else: # do not need to care of wmask + pin_pos = pin.bc() + mid = vector(pin_pos.x, trunk_offset.y) + self.add_path(self.vertical_layer, [pin_pos, mid]) + self.add_via_center(layers=self.layer_stack, + offset=mid, + directions=self.directions) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin.bc()) + else: # port 1, situation different, top need shift + if non_preferred_route: + # Add the horizontal trunk on the vertical layer! + self.add_path(self.vertical_layer, + [vector(min_x - half_layer_width, trunk_offset.y), + vector(max_x + half_layer_width, trunk_offset.y)]) + + # Route each pin to the trunk + for pin in pins: + if pin.cy() < trunk_offset.y: + if self.check_need_jog(pin.name) and (OPTS.write_size != OPTS.word_size):# for port0, only pin above track needs to check + # right first + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin.rc()) + point_1 = vector(pin.center().x + pitch, pin.center().y) + self.add_path(self.vertical_layer, [pin.rc(), point_1]) + # straight out of bank area + self.add_via_stack_center(from_layer=self.vertical_layer, + to_layer=self.vertical_layer, + offset=point_1) + point_2 = vector(point_1.x, point_1.y + 5.6) + self.add_path(self.vertical_layer, [point_1, point_2]) + # then left + self.add_via_stack_center(from_layer=self.vertical_layer, + to_layer=self.vertical_layer, + offset=point_2) + point_3 = vector(pin.center().x, pin.center().y + 5.6) + self.add_path(self.vertical_layer, [point_2, point_3]) + # back to normal + self.add_via_stack_center(from_layer=self.vertical_layer, + to_layer=self.vertical_layer, + offset=point_3) + mid = vector(point_3.x, trunk_offset.y) + self.add_path(self.vertical_layer, [point_3, mid]) + else: # do not need to care of wmask + pin_pos = pin.uc() + # No bend needed here + mid = vector(pin_pos.x, trunk_offset.y) + self.add_path(self.vertical_layer, [pin_pos, mid]) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin.uc()) + else: + pin_pos = pin.center() + mid = vector(pin_pos.x - 0.1, trunk_offset.y) + self.add_path(self.vertical_layer, [vector(pin.uc().x - 0.1, pin.uc().y), mid]) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin_pos) + else: + # Add the horizontal trunk + self.add_path(self.horizontal_layer, + [vector(min_x, trunk_offset.y), + vector(max_x, trunk_offset.y)]) + + # Route each pin to the trunk + for pin in pins: + # Find the correct side of the pin + if pin.cy() < trunk_offset.y: + if self.check_need_jog(pin.name) and (OPTS.write_size != OPTS.word_size):# for port0, only pin above track needs to check + # right first + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin.rc()) + point_1 = vector(pin.center().x + pitch, pin.center().y) + self.add_path(self.vertical_layer, [pin.rc(), point_1]) + # straight out of bank area + self.add_via_stack_center(from_layer=self.vertical_layer, + to_layer=self.vertical_layer, + offset=point_1) + point_2 = vector(point_1.x, point_1.y + 5.6) + self.add_path(self.vertical_layer, [point_1, point_2]) + # then left + self.add_via_stack_center(from_layer=self.vertical_layer, + to_layer=self.vertical_layer, + offset=point_2) + point_3 = vector(pin.center().x, pin.center().y + 5.6) + self.add_path(self.vertical_layer, [point_2, point_3]) + # back to normal + self.add_via_stack_center(from_layer=self.vertical_layer, + to_layer=self.vertical_layer, + offset=point_3) + mid = vector(point_3.x, trunk_offset.y) + self.add_path(self.vertical_layer, [point_3, mid]) + self.add_via_center(layers=self.layer_stack, + offset=mid, + directions=self.directions) + else: # do not need to care of wmask + pin_pos = pin.uc() + mid = vector(pin_pos.x, trunk_offset.y) + self.add_path(self.vertical_layer, [pin_pos, mid]) + self.add_via_center(layers=self.layer_stack, + offset=mid, + directions=self.directions) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin.uc()) + else: + pin_pos = pin.center() + mid = vector(pin_pos.x - 0.1, trunk_offset.y) + self.add_path(self.vertical_layer, [vector(pin.uc().x - 0.1, pin.uc().y), mid]) + self.add_via_center(layers=self.layer_stack, + offset=mid, + directions=self.directions) + self.add_via_stack_center(from_layer=pin.layer, + to_layer=self.vertical_layer, + offset=pin_pos) + def add_horizontal_trunk_route(self, pins, trunk_offset, diff --git a/compiler/base/hierarchy_layout.py b/compiler/base/hierarchy_layout.py index 31802ee70..f8d371a90 100644 --- a/compiler/base/hierarchy_layout.py +++ b/compiler/base/hierarchy_layout.py @@ -1913,6 +1913,42 @@ def add_io_pin(self, instance, pin_name, new_name, start_layer=None, directions= # 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_m2(self, name, loc, directions=None, start_layer="m1"): + # same function like normal one, but add power pin at m2 + # 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 == "m2": + 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="m2", + 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="m2", + 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 @@ def add_perimeter_pin(self, name, pin, side, bbox): layer=layer, offset=peri_pin_loc) - def add_dnwell(self, bbox=None, inflate=1): + def add_dnwell(self, bbox=None, inflate=1, route_option="classic"): """ Create a dnwell, along with nwell moat at border. """ if "dnwell" not in tech_layer: @@ -2049,11 +2085,20 @@ def add_dnwell(self, bbox=None, inflate=1): 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)]) @@ -2063,9 +2108,9 @@ def add_dnwell(self, bbox=None, inflate=1): tap_spacing = 2 nwell_offset = vector(self.nwell_width, self.nwell_width) - # Every nth tap is connected to gnd + # Every nth tap is connected to vdd period = 5 - + moat_pins = [] # BOTTOM count = 0 loc = ll + nwell_offset.scale(tap_spacing, 0) @@ -2080,9 +2125,10 @@ def add_dnwell(self, bbox=None, inflate=1): to_layer="m1", offset=loc) else: - self.add_power_pin(name="vdd", - loc=loc, - start_layer="li") + pin = self.add_power_pin(name="vdd", + loc=loc, + start_layer="li") + moat_pins.append(pin) count += 1 loc += nwell_offset.scale(tap_spacing, 0) @@ -2100,9 +2146,10 @@ def add_dnwell(self, bbox=None, inflate=1): to_layer="m1", offset=loc) else: - self.add_power_pin(name="vdd", - loc=loc, - start_layer="li") + pin = self.add_power_pin(name="vdd", + loc=loc, + start_layer="li") + moat_pins.append(pin) count += 1 loc += nwell_offset.scale(tap_spacing, 0) @@ -2120,9 +2167,15 @@ def add_dnwell(self, bbox=None, inflate=1): to_layer="m2", offset=loc) else: - self.add_power_pin(name="vdd", - loc=loc, - start_layer="li") + if route_option == "classic": + pin = self.add_power_pin(name="vdd", + loc=loc, + start_layer="li") + elif route_option == "quality": + pin = self.add_power_pin_m2(name="vdd", + loc=loc, + start_layer="li") + moat_pins.append(pin) count += 1 loc += nwell_offset.scale(0, tap_spacing) @@ -2140,14 +2193,21 @@ def add_dnwell(self, bbox=None, inflate=1): to_layer="m2", offset=loc) else: - self.add_power_pin(name="vdd", - loc=loc, - start_layer="li") + if route_option == "classic": + pin = self.add_power_pin(name="vdd", + loc=loc, + start_layer="li") + elif route_option == "quality": + pin = self.add_power_pin_m2(name="vdd", + loc=loc, + start_layer="li") + moat_pins.append(pin) count += 1 loc += nwell_offset.scale(0, tap_spacing) - # Add the gnd ring + # Add the vdd ring self.add_ring([ll, ur]) + return moat_pins def add_ring(self, bbox=None, width_mult=8, offset=0): """ diff --git a/compiler/gdsMill/gdsMill/vlsiLayout.py b/compiler/gdsMill/gdsMill/vlsiLayout.py index b87aea776..5dec6a803 100644 --- a/compiler/gdsMill/gdsMill/vlsiLayout.py +++ b/compiler/gdsMill/gdsMill/vlsiLayout.py @@ -742,6 +742,9 @@ def getAllPinShapes(self, pin_name): Search for a pin label and return ALL the enclosing rectangles on the same layer as the pin label. """ + #debug + for pin in self.pins: + print(pin) shape_list = [] pin_map = self.pins[pin_name] for pin_list in pin_map: diff --git a/compiler/modules/sram_1bank.py b/compiler/modules/sram_1bank.py index 7d1aec3fc..da9c6a8dd 100644 --- a/compiler/modules/sram_1bank.py +++ b/compiler/modules/sram_1bank.py @@ -5,6 +5,7 @@ # (acting for and on behalf of Oklahoma State University) # All rights reserved. # +# This aim to change the position of the submodule import datetime from math import ceil from importlib import import_module, reload @@ -17,6 +18,7 @@ from openram.sram_factory import factory from openram.tech import spice from openram import OPTS, print_time +import re class sram_1bank(design, verilog, lef): @@ -51,6 +53,11 @@ def __init__(self, name, sram_config): # 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 = [] + # vdd pins on moat, list of pins + self.moat_pins = [] + def add_pins(self): """ Add pins for entire SRAM. """ @@ -206,15 +213,15 @@ def create_netlist(self): if not OPTS.is_unit_test: print_time("Submodules", datetime.datetime.now(), start_time) - def create_layout(self): + def create_layout(self, position_add=0, mod=0, route_option="classic"): """ Layout creation """ start_time = datetime.datetime.now() - self.place_instances() + self.place_instances_changeable(position_add=position_add) if not OPTS.is_unit_test: print_time("Placement", datetime.datetime.now(), start_time) start_time = datetime.datetime.now() - self.route_layout() + self.route_layout(mod=mod, route_option=route_option) if not OPTS.is_unit_test: print_time("Routing", datetime.datetime.now(), start_time) @@ -242,6 +249,38 @@ def create_layout(self): def create_modules(self): debug.error("Must override pure virtual function.", -1) + def route_supplies_constructive(self, bbox=None): + """ Corresponding supply router for io_pin_placer """ + # 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, + moat_pins=self.moat_pins) + + # add power rings / side pins + if OPTS.supply_pin_type in ["top", "bottom", "right", "left"]: + rtr.add_side_pin("vdd") + rtr.add_side_pin("gnd")# noraml gnd name + elif OPTS.supply_pin_type == "ring": + rtr.add_ring_pin("vdd") + rtr.add_ring_pin("gnd")# normal gnd name + else: + debug.warning("Side supply pins aren't created.") + + # Prepare the inside power pins (all power pins of submodules), at m3 + for pin_name in ["vdd", "gnd"]: + for inst in self.insts: + self.copy_power_pins(inst, pin_name) + # Route all the power pins + #rtr.route_inside() # only connecting the power pins of inside submodules with each other, moat pins will connect to the ring + rtr.route_outside(io_pin_names=self.pins_to_route) + # route moat vdds + #rtr.route_moat(self.pins_to_route) + def route_supplies(self, bbox=None): """ Route the supply grid and connect the pins to them. """ @@ -318,54 +357,68 @@ def route_supplies(self, bbox=None): # Grid is left with many top level pins pass - def route_escape_pins(self, bbox=None): + def route_escape_pins(self, bbox=None, mod=0, route_option="classic"): """ 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)) - - from openram.router import signal_escape_router as router - rtr = router(layers=self.m3_stack, - bbox=bbox, - design=self) - rtr.route(pins_to_route) + 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 + # mod Use for control which edge/position the pins(dout) will be placed + # 0 -> default + # 1 -> all top/bottom + # 2 -> all left/right + rtr = router(layers=self.m3_stack, + bbox=bbox, + design=self, + mod=mod) + rtr.route(self.pins_to_route) + elif route_option == "quality": + # use io_pin_placer + # put the IO pins at the edge + from openram.router.io_pin_placer import io_pin_placer as placer + pl = placer(layers=self.m3_stack, + bbox=bbox, + design=self) + for name in self.pins_to_route: + debug.warning("pins_to_route pins -> {0}".format(name)) + 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 """ @@ -779,10 +832,10 @@ def create_modules(self): else: self.num_spare_cols = 0 - def place_instances(self): + def place_instances_changeable(self, position_add=0): """ This places the instances for a single bank SRAM with control - logic and up to 2 ports. + logic and up to 2 ports, but be able to change the io the dff position """ # No orientation or offset @@ -844,6 +897,9 @@ def place_instances(self): self.route_dffs(add_routes=False) self.remove_layout_pins() + for port in self.all_ports: + # Add the extra position + self.data_bus_size[port] += position_add # Re-place with the new channel size self.place_dffs() @@ -856,7 +912,6 @@ def place_row_addr_dffs(self): x_offset = self.control_logic_insts[port].rx() - self.row_addr_dff_insts[port].width # It is above the control logic and the predecoder array y_offset = max(self.control_logic_insts[port].uy(), self.bank.predecoder_top) - self.row_addr_pos[port] = vector(x_offset, y_offset) self.row_addr_dff_insts[port].place(self.row_addr_pos[port]) @@ -865,7 +920,7 @@ def place_row_addr_dffs(self): # The row address bits are placed above the control logic aligned on the left. x_offset = self.control_pos[port].x - self.control_logic_insts[port].width + self.row_addr_dff_insts[port].width # If it can be placed above the predecoder and below the control logic, do it - y_offset = self.bank.predecoder_bottom + y_offset = min(self.control_logic_insts[port].by(), self.bank.predecoder_bottom) self.row_addr_pos[port] = vector(x_offset, y_offset) self.row_addr_dff_insts[port].place(self.row_addr_pos[port], mirror="XY") @@ -1051,7 +1106,7 @@ def add_layout_pins(self, add_vias=True): "spare_wen{0}[{1}]".format(port, bit), start_layer=pin_layer) - def route_layout(self): + def route_layout(self, mod=0, route_option="classic"): """ Route a single bank SRAM """ self.route_clk() @@ -1067,7 +1122,7 @@ def route_layout(self): self.add_layout_pins() # Some technologies have an isolation - self.add_dnwell(inflate=2.5) + self.moat_pins = self.add_dnwell(inflate=2.5, route_option=route_option) init_bbox = self.get_bbox() # Route the supplies together and/or to the ring/stripes. @@ -1075,10 +1130,13 @@ def route_layout(self): if OPTS.perimeter_pins: # We now route the escape routes far enough out so that they will # reach past the power ring or stripes on the sides - self.route_escape_pins(init_bbox) - if OPTS.route_supplies: - self.route_supplies(init_bbox) + self.route_escape_pins(bbox=init_bbox, mod=mod, route_option=route_option) + if OPTS.route_supplies: + if route_option == "classic": + self.route_supplies(init_bbox) + elif route_option == "quality": # quality + self.route_supplies_constructive(init_bbox) def route_dffs(self, add_routes=True): @@ -1114,11 +1172,12 @@ def route_col_addr_dffs(self, port): if port == 0: offset = vector(self.control_logic_insts[port].rx() + self.dff.width, - - self.data_bus_size[port] + 2 * self.m3_pitch) + self.bank_inst.by() - self.col_addr_size * self.m3_pitch)# higher offset to avoid possible overlap with data dffs channel routing cr = channel_route(netlist=route_map, offset=offset, layer_stack=layer_stack, - parent=self) + parent=self, + dff_area=True)# this is a special value to handle dff areas, should be true when routing col_dff/dffs # This causes problem in magic since it sometimes cannot extract connectivity of instances # with no active devices. self.add_inst(cr.name, cr) @@ -1130,7 +1189,8 @@ def route_col_addr_dffs(self, port): cr = channel_route(netlist=route_map, offset=offset, layer_stack=layer_stack, - parent=self) + parent=self, + dff_area=True)# this is a special value to handle dff areas, should be true when routing col_dff/dffs # This causes problem in magic since it sometimes cannot extract connectivity of instances # with no active devices. self.add_inst(cr.name, cr) @@ -1165,23 +1225,18 @@ def route_data_dffs(self, port, add_routes): route_map.extend(list(zip(bank_pins, dff_pins))) if len(route_map) > 0: - # This layer stack must be different than the column addr dff layer stack - layer_stack = self.m3_stack + layer_stack = self.m2_stack if port == 0: - # This is relative to the bank at 0,0 or the s_en which is routed on M3 also - if "s_en" in self.control_logic_insts[port].mod.pin_map: - y_bottom = min(0, self.control_logic_insts[port].get_pin("s_en").by()) - else: - y_bottom = 0 - - y_offset = y_bottom - self.data_bus_size[port] + 2 * self.m3_pitch + # for port 0, the offset of first track in channel router is fixed + y_offset = self.data_dff_insts[port].uy() + 6 * self.m3_pitch offset = vector(self.control_logic_insts[port].rx() + self.dff.width, y_offset) cr = channel_route(netlist=route_map, offset=offset, layer_stack=layer_stack, - parent=self) + parent=self, + dff_area=True)# this is a special value to handle dff areas, should be true when routing col_dff/dffs if add_routes: # This causes problem in magic since it sometimes cannot extract connectivity of instances # with no active devices. @@ -1189,19 +1244,84 @@ def route_data_dffs(self, port, add_routes): self.connect_inst([]) # self.add_flat_inst(cr.name, cr) else: - self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap + # return the real channel width that min. should be + # the bottom of w_en or s_en + if ("s_en" in self.control_logic_insts[port].mod.pin_map) and ("w_en" in self.control_logic_insts[port].mod.pin_map):# rw + y_bottom = min(0, self.control_logic_insts[port].get_pin("s_en").by(), self.control_logic_insts[port].get_pin("w_en").by()) + elif "w_en" in self.control_logic_insts[port].mod.pin_map:# w + y_bottom = min(0, self.control_logic_insts[port].get_pin("w_en").by()) + else: + y_bottom = 0 + + if len(self.all_ports) == 1: # only 1 port at bottom + extra_offset = 6 * self.m3_pitch + (self.bank_inst.by() - (y_bottom - self.m4_nonpref_pitch)) + else: # 2 ports, row address decoder needs to be considered + # for port 0 + # determine the most right dffs + if self.num_spare_cols: # if we have spare regs + dff_right_x = self.spare_wen_dff_insts[0].rx() + else: # data dffs + dff_right_x = self.data_dff_insts[0].rx() + # check if row address dffs are overlaped with dff area, bank position as reference + if self.bank_inst.rx() < (dff_right_x + 2 * self.m4_pitch): + debug.warning("m4 pitch ----> {0}".format(self.m4_pitch)) + debug.warning("m3 pitch ----> {0}".format(self.m3_pitch)) + debug.warning("m4_non_pref pitch ----> {0}".format(self.m4_nonpref_pitch)) + debug.warning("lower row addr dff: {0}".format(self.row_addr_dff_insts[1].by())) + debug.warning("higher row addr dff: {0}".format(self.row_addr_dff_insts[1].uy())) + # check the most lower one betwenn control signal and address dff + if y_bottom < self.row_addr_dff_insts[1].by(): + y_bottom_most = y_bottom + else: + y_bottom_most = self.row_addr_dff_insts[1].by() + # the upper track should below the lower one + extra_offset = 6 * self.m3_pitch + (self.bank_inst.by() - (y_bottom_most - self.m4_nonpref_pitch)) + else: # do not need take care of address dff 1, since it's far away + extra_offset = 6 * self.m3_pitch + (self.bank_inst.by() - (y_bottom - self.m4_nonpref_pitch)) + debug.warning("extra_offset->{0}".format(extra_offset)) + debug.warning("channel_height->{0}".format(cr.height)) + debug.warning("self.col_addr_bus_size->{0}".format(self.col_addr_bus_size[port])) + debug.warning("self.databusgap->{0}".format(self.data_bus_gap)) + self.data_bus_size[port] = max((cr.height + extra_offset), self.col_addr_bus_size[port]) + self.data_bus_gap else: - if "s_en" in self.control_logic_insts[port].mod.pin_map: - y_top = max(self.bank.height, self.control_logic_insts[port].get_pin("s_en").uy()) + # for port1, the offset of first track in channel router needs to check first, make sure no overlap with control signal & address dff + if ("s_en" in self.control_logic_insts[port].mod.pin_map) and ("w_en" in self.control_logic_insts[port].mod.pin_map): + y_top = max(self.bank.height, self.control_logic_insts[port].get_pin("s_en").uy(), self.control_logic_insts[port].get_pin("w_en").uy()) + y_offset = y_top + 6 * self.m3_pitch + elif "w_en" in self.control_logic_insts[port].mod.pin_map: + y_top = max(self.bank.height, self.control_logic_insts[port].get_pin("w_en").uy()) + y_offset = y_top + self.m3_pitch# it's fine, since w port doesn't have dout signals else: y_top = self.bank.height - y_offset = y_top + self.m3_pitch + y_offset = y_top + 6 * self.m3_pitch + # check the offset overlap with address dff 0 or not + if self.num_spare_cols: # if we have spare regs + dff_left_x = self.spare_wen_dff_insts[1].lx() + else: # data dffs + dff_left_x = self.data_dff_insts[1].lx() + # check if row address dffs are overlaped with dff area + if self.bank_inst.lx() > (dff_left_x - 2 * self.m4_pitch): + debug.warning("m4 pitch ----> {0}".format(self.m4_pitch)) + debug.warning("m3 pitch ----> {0}".format(self.m3_pitch)) + debug.warning("m4_non_pref pitch ----> {0}".format(self.m4_nonpref_pitch)) + # the bottom track should also above row address decoder + if y_offset > self.row_addr_dff_insts[0].uy() + self.m4_nonpref_pitch: + # do not need change since first track is high enough + extra_offset = y_offset - self.bank.height # height could be use since bank at 0,0 + else: # make it higher tham row address decoder + extra_offset = self.row_addr_dff_insts[0].uy() + self.m4_nonpref_pitch - self.bank_inst.height + # update the new y_offset + y_offset = self.row_addr_dff_insts[0].uy() + self.m4_nonpref_pitch + else: # do not need to take care address dff0, since it's far away + extra_offset = y_offset - self.bank.height # height could be use since bank at 0,0 + offset = vector(0, y_offset) cr = channel_route(netlist=route_map, offset=offset, layer_stack=layer_stack, - parent=self) + parent=self, + dff_area=True)# this is a special value to handle dff areas, should be true when routing col_dff/dffs if add_routes: # This causes problem in magic since it sometimes cannot extract connectivity of instances # with no active devices. @@ -1209,7 +1329,10 @@ def route_data_dffs(self, port, add_routes): self.connect_inst([]) # self.add_flat_inst(cr.name, cr) else: - self.data_bus_size[port] = max(cr.height, self.col_addr_bus_size[port]) + self.data_bus_gap + # return the real channel width that min. should be + # 2 ports, row address decoder needs to be considered + # for port 1 + self.data_bus_size[port] = max((cr.height + extra_offset), self.col_addr_bus_size[port]) + self.data_bus_gap def route_clk(self): """ Route the clock network """ diff --git a/compiler/options.py b/compiler/options.py index d97ee70ed..4d1fb861f 100644 --- a/compiler/options.py +++ b/compiler/options.py @@ -52,6 +52,8 @@ class options(optparse.Values): words_per_row = None num_spare_rows = 0 num_spare_cols = 0 + # Route approach + route_approach = "classic"# "classic" or "quality" ################### # ROM configuration options diff --git a/compiler/router/io_pin_placer.py b/compiler/router/io_pin_placer.py new file mode 100644 index 000000000..f1f8dbf7d --- /dev/null +++ b/compiler/router/io_pin_placer.py @@ -0,0 +1,449 @@ +# 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.base.vector3d import vector3d +from openram import OPTS +from .graph import graph +from .graph_shape import graph_shape +from .router import router +import re + +class io_pin_placer(router): + + def __init__(self, layers, design, bbox=None): + + # `router` is the base router class + router.__init__(self, layers, design, bbox) + + # New pins are the side supply pins + self.new_pins = {} + + # added_io_pins + self.io_pins_added_left = [] + self.io_pins_added_right = [] + self.io_pins_added_up = [] + self.io_pins_added_down = [] + + # fake_pins, use for rename + self.io_pins_fake =[] + + + def get_closest_edge(self, point): + """ Return a point's the closest edge and the edge's axis direction. """ + + ll, ur = self.bbox + + # 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", True + if min_diff == ll_diff_y: + return "bottom", False + if min_diff == ur_diff_x: + return "right", True + return "top", False + + + def initial_position(self, pins): # pins is list [] + """ Set the IO pin center at the perimeter """ + pattern_clk = r'^clk' + pattern_addr0 = r'^addr0' + pattern_addr1 = r'^addr1' + pattern_dout0 = r'^dout0' + pattern_dout1 = r'^dout1' + pattern_din = r'^din' + pattern_wmask = r'^wmask' + pattern_spare_wen = r'^spare_wen' + pattern_web = r'^web' + pattern_csb = r'^csb' + for pin in pins: + c = pin.center() + # Find the closest edge + edge, vertical = self.get_closest_edge(c) + if re.match(pattern_clk, pin.name):# clk, should be placed at vertical edge + if edge == "bottom" or edge == "left":#clk0 + edge = "left" + elif edge == "top" or edge == "right":#clk1 + edge = "right" + vertical = True + self.store_position(pin, edge, vertical) + if re.match(pattern_addr0, pin.name): # all the addr0[] should be placed at left edge + if edge == "top" or edge == "left": + edge = "left" + vertical = True + elif edge == "bottom": # but for big sram, addr0[] may have pins at bottom, which is allowed + vertical = False + self.store_position(pin, edge, vertical) + if re.match(pattern_addr1, pin.name): # all the addr1[] should be placed at right edge + if edge == "bottom" or edge == "right": + edge = "right" + vertical = True + elif edge == "top": # but for big sram, addr1[] may have pins at top, which is allowed + vertical = False + self.store_position(pin, edge, vertical) + if re.match(pattern_din, pin.name): # din + self.store_position(pin, edge, vertical) + if re.match(pattern_wmask, pin.name): # wmask + self.store_position(pin, edge, vertical) + if re.match(pattern_spare_wen, pin.name): # spare_wen + self.store_position(pin, edge, vertical) + if re.match(pattern_csb, pin.name):# csb + self.store_position(pin, edge, vertical) + if re.match(pattern_web, pin.name): # web + self.store_position(pin, edge, vertical) + # special handle the dout pins, if r/rw ports + for pin in pins: + if re.match(pattern_dout0, pin.name): + edge = "bottom" + vertical = False + self.store_dout_position(pin, edge, vertical) + if re.match(pattern_dout1, pin.name): + edge = "top" + vertical = False + self.store_dout_position(pin, edge, vertical) + + + def check_overlap(self, pin_position, edge): + """ Return the suggested position to aviod overlap """ + ll, ur = self.bbox + c = pin_position # original source pin center + offset = 0.95 + 0.19 # FIX: this is the magic number to overcome the ovetflow problem at the boundary, may need a method + add_distance = 0 + + if edge == "bottom": + fake_center = vector(c.x, ll.y - self.track_wire * 2 + offset) + pin_to_close = any(abs(pin_added.center().x - fake_center.x) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_down) + via_to_close = False + # if cannot direct place below the source pin, need move towards right, and ensure the min. distance between vias + while pin_to_close or via_to_close: + debug.warning("overlap, changing position") + add_distance = add_distance + 0.1 + fake_center = vector(c.x + add_distance, ll.y - self.track_wire * 2 + offset) + pin_to_close = any(abs(pin_added.center().x - fake_center.x) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_down) + via_to_close = abs(fake_center.x - c.x) < 0.6 + if edge == "top": + fake_center = vector(c.x, ur.y + self.track_wire * 2 - offset) + pin_to_close = any(abs(pin_added.center().x - fake_center.x) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_up) + via_to_close = False + # if cannot direct place below the source pin, need move towards right, and ensure the min. distance between vias + while pin_to_close or via_to_close: + debug.warning("overlap, changing position") + add_distance = add_distance + 0.1 + fake_center = vector(c.x + add_distance, ur.y + self.track_wire * 2 - offset) + pin_to_close = any(abs(pin_added.center().x - fake_center.x) < (0.4 + self.half_wire * 4)for pin_added in self.io_pins_added_up) + via_to_close = abs(fake_center.x - c.x) < 0.6 + + return fake_center + + + def store_dout_position(self, pin, edge, vertical): + pin_position = pin.center() + pin_position = self.check_overlap(pin_position, edge) + # store the center position, rect, layer of fake pin, here make sure the pin in the gds will be big enough + layer = self.get_layer(int(not vertical)) + half_wire_vector = vector([self.half_wire] * 2) + nll = pin_position - half_wire_vector - half_wire_vector + nur = pin_position + half_wire_vector + half_wire_vector + rect = [nll, nur] + #fake_pin = [pin.name() + "_" + "fake", pin_position, rect, layer] + fake_pin = graph_shape(name=pin.name + "_" + "fake", + rect=rect, + layer_name_pp=layer) + + if edge == "left": + self.io_pins_added_left.append(fake_pin) + elif edge == "bottom": + self.io_pins_added_down.append(fake_pin) + elif edge == "right": + self.io_pins_added_right.append(fake_pin) + elif edge == "top": + self.io_pins_added_up.append(fake_pin) + + self.io_pins_fake.append(fake_pin) + debug.warning("pin added: {0}".format(fake_pin)) + + + def store_position(self, pin, edge, vertical): # also need to store the source pin + ll, ur = self.bbox + c = pin.center() + pattern_clk = r'^clk' + pattern_csb = r'^csb' + offset = 0.95 + 0.19 # FIX: this is the magic number to overcome the ovetflow problem at the boundary, may need a method + if edge == "left": + fake_center = vector(ll.x - self.track_wire * 2 + offset, c.y) + if re.match(pattern_clk, pin.name): # clk0 need to be higher at left edge, 0.32 is magic number + fake_center = vector(ll.x - self.track_wire * 2 + offset, c.y + 0.32) + if re.match(pattern_csb, pin.name): # csb0 need to be lower at left edge, 0.32 is magic number + fake_center = vector(ll.x - self.track_wire * 2 + offset, c.y - 0.32) + if edge == "bottom": + fake_center = vector(c.x, ll.y - self.track_wire * 2 + offset) + if edge == "right": + fake_center = vector(ur.x + self.track_wire * 2 - offset, c.y) + if re.match(pattern_clk, pin.name): # clk1 need to be lower at right edge, 0.32 is magic number + fake_center = vector(ur.x + self.track_wire * 2 - offset, c.y - 0.32) + if re.match(pattern_csb, pin.name): # csb0 need to be higher at right edge, 0.32 is magic number + fake_center = vector(ur.x + self.track_wire * 2 - offset, c.y + 0.32) + if edge == "top": + fake_center = vector(c.x, ur.y + self.track_wire * 2 - offset) + # store the center position, rect, layer of fake pin, here make sure the pin in the gds will be big enough + layer = self.get_layer(int(not vertical)) + half_wire_vector = vector([self.half_wire] * 2) + nll = fake_center - half_wire_vector - half_wire_vector + nur = fake_center + half_wire_vector + half_wire_vector + rect = [nll, nur] + #fake_pin = [pin.name() + "_" + "fake", fake_center, rect, layer] + fake_pin = graph_shape(name=pin.name + "_" + "fake", + rect=rect, + layer_name_pp=layer) + + if edge == "left": + self.io_pins_added_left.append(fake_pin) + elif edge == "bottom": + self.io_pins_added_down.append(fake_pin) + elif edge == "right": + self.io_pins_added_right.append(fake_pin) + elif edge == "top": + self.io_pins_added_up.append(fake_pin) + + self.io_pins_fake.append(fake_pin) + debug.warning("pin added: {0}".format(fake_pin)) + + + def add_io_pins(self, pin_names): + """ Add IO pins on the edges WITHOUT routing them. """ + debug.info(1, "Adding IO pins on the perimeter...") + + # Prepare GDS reader (if necessary for pin/blockage identification) + self.prepare_gds_reader() + + # Find pins to be added (without routing) + for name in pin_names: + self.find_pins(name)# this will add the pins to the self.pins + + # inital position + pin_list = [] + for name in self.pins: + pin = next(iter(self.pins[name])) + pin_list.append(pin) + + self.initial_position(pin_list) + + # Change IO pin names, which means "internal name" will be used, and internal io pins will not have label such as "dout0[0]" + self.replace_layout_pins(pin_names) + + + def add_io_pins_connected(self, pin_names): + """ Add IO pins on the edges WITH routing them. """ + debug.info(1, "Adding IO pins on the perimeter...") + + # Prepare GDS reader (if necessary for pin/blockage identification) + self.prepare_gds_reader() + + # Find pins to be added (without routing) + for name in pin_names: + self.find_pins(name)# this will add the pins to the self.pins + debug.warning("the pins in pin_name -> {0}".format(name)) + + # inital position + pin_list = [] + for name in self.pins: + pin = next(iter(self.pins[name])) + pin_list.append(pin) + debug.warning("the pins in self.pins -> {0}".format(name)) + + self.initial_position(pin_list) + + # add fake io pins at the perimeter, which will be used for routing + for fake_pin in self.io_pins_fake: + self.design.add_layout_pin(text=fake_pin.name, + layer=fake_pin.layer, + offset=fake_pin.ll(), + width=fake_pin.width(), + height=fake_pin.height()) + + # connect the source_pin and io_pin(target) + self.connect_pins(pin_names) + + # remove the fake pin before change the name, in order to avoid possible problem + for fake_pin in self.io_pins_fake: + self.remove_io_pins(fake_pin.name) + + # Change IO pin names, which means "internal name" will be used, and internal io pins will not have label such as "dout0[0]" + self.replace_layout_pins(pin_names) + + + def connect_pins(self, pin_names): # pin_names should be a list + """ Add IO pins on the edges, and connect them to the internal one, not-graph like process """ + debug.info(1, "connecting to io pins...") + pattern_dout = r'^dout' + for pin_name in pin_names: + # get pin pairs ready + source_pin = next(iter(self.pins[pin_name])) + for fake_pin in self.io_pins_fake: + if pin_name + "_" + "fake" == fake_pin.name: + target_pin = fake_pin + break + # special hanlde dout pins + if re.match(pattern_dout, pin_name): + number_str = re.findall(r'\[(\d+)\]', pin_name) + if number_str: + number = int(number_str[0]) + if number % 2 == 0: + is_up = True + else: + is_up = False + point_list = self.decide_point(source_pin, target_pin, is_up) + self.add_wire(point_list) + # other pins + else: + debug.warning("source{0}".format(source_pin.center())) + debug.warning("target{0}".format(target_pin.center())) + point_list = self.decide_point(source_pin, target_pin) + debug.warning("point_list->{0}".format(point_list)) + self.add_wire(point_list) + + + def add_wire(self, point_list): + if len(point_list) == 2: + # direct connect + self.add_line(point_list[0], point_list[1]) + elif len(point_list) == 4: + # intermediate points + self.add_line(point_list[0], point_list[1]) + self.add_via(point_list[1]) + self.add_line(point_list[1], point_list[2]) + self.add_via(point_list[2]) + self.add_line(point_list[2], point_list[3]) + + + def add_line(self, point_1, point_2): + if round(point_1.y, 3) == round(point_2.y, 3): + # horizontal m3 + self.design.add_path(self.get_layer(False), [point_1, point_2]) + else: + # vertical m4 + self.design.add_path(self.get_layer(True), [point_1, point_2]) + + + def add_via(self, point): + # currently only m3-m4 vias are supported in this method + # usd in order to make "z" shape routing only + self.design.add_via_stack_center(from_layer=self.get_layer(False), + to_layer=self.get_layer(True), + offset=point) + + + def add_start_via(self, point): + # currently only m3-m4 vias are supported in this method + # used in source_pin only + self.design.add_via_stack_center(from_layer=self.get_layer(False), + to_layer=self.get_layer(True), + offset=point) + + + def add_big_plate(self, layer, offset, width, height): + # add rectagle at internal source pin, which avoid jog/non-preferred routing, but could implement shift-routing + # used in source_pin only + # offset->vertor(...), it is bottom-left position + self.design.add_rect(layer=layer, + offset=offset, + width=width, + height=height) + + + def decide_point(self, source_pin, target_pin, is_up=False): + ll, ur = self.bbox + offset = 0.95 + 0.19 # FIX: this is the magic number to overcome the ovetflow problem at the boundary, may need a method + pattern_clk = r'^clk' + pattern_csb = r'^csb' + # internal -> left + if round(target_pin.center().x, 3) == round(ll.x - self.track_wire * 2 + offset, 3): + # special handle clk0 + if re.match(pattern_clk, source_pin.name): + return [vector(source_pin.rc().x, source_pin.rc().y + 0.32), target_pin.lc()]# 0.32 should be same in initial_position + # special handel csb0 + elif re.match(pattern_csb, source_pin.name): + return [vector(source_pin.rc().x, source_pin.rc().y - 0.32), target_pin.lc()]# 0.32 should be same in initial_position + else: + # direct connect possible + return [source_pin.rc(), target_pin.lc()] # FIX: not sure if shape overlap in met3 allowed, but seems OK + # internal -> right + if round(target_pin.center().x, 3) == round(ur.x + self.track_wire * 2 - offset, 3): + # special handel clk1 + if re.match(pattern_clk, source_pin.name): + return [vector(source_pin.lc().x, source_pin.lc().y - 0.32), target_pin.rc()]# 0.32 should be same in initial_position + # special handle csb0 + elif re.match(pattern_csb, source_pin.name): + return [vector(source_pin.lc().x, source_pin.lc().y + 0.32), target_pin.rc()]# 0.32 should be same in initial_position + else: + # direct connect possible + return [source_pin.lc(), target_pin.rc()] + # internal -> top, need to add start_via m3->m4 + if round(target_pin.center().y, 3) == round(ur.y + self.track_wire * 2 - offset, 3): + self.add_start_via(source_pin.center()) + if round(target_pin.center().x, 3) == round(source_pin.center().x, 3): + # direct connect possible + return [source_pin.bc(), target_pin.uc()] + else: + # need intermediate point + #via_basic_y = self.design.bank.height + 3 # 3 is magic number, make sure out of bank area + via_basic_y = self.design.bank_inst.uy() + 3 * self.design.m3_pitch + is_up = not is_up# Be attention, for channel at the top, the is_up should be inverted! Otherwise will cause overlap! + if is_up: + #via_basic_y = via_basic_y + 0.5 + via_basic_y = via_basic_y + self.design.m3_pitch + else: + #via_basic_y = via_basic_y - 0.5 + via_basic_y = via_basic_y - self.design.m3_pitch + point_1 = vector(source_pin.center().x, via_basic_y) + point_2 = vector(target_pin.center().x, via_basic_y) + return [source_pin.bc(), point_1, point_2, target_pin.uc()] + # internal -> bottom, need to add start_via m3->m4 + if round(target_pin.center().y, 3) == round(ll.y - self.track_wire * 2 + offset, 3): + self.add_start_via(source_pin.center()) + if round(target_pin.center().x, 3) == round(source_pin.center().x, 3): + # direct connect possible + return [source_pin.uc(), target_pin.bc()] + else: + # need intermediate point + #via_basic_y = ll.y + 22 # 22 is magic number, make sure out of dff area + via_basic_y = self.design.data_dff_insts[0].uy() + 3 * self.design.m3_pitch + if is_up: + #via_basic_y = via_basic_y + 0.5 + via_basic_y = via_basic_y + self.design.m3_pitch + else: + #via_basic_y = via_basic_y - 0.5 + via_basic_y = via_basic_y - self.design.m3_pitch + point_1 = vector(source_pin.center().x, via_basic_y) + point_2 = vector(target_pin.center().x, via_basic_y) + return [source_pin.uc(), point_1, point_2, target_pin.bc()] + + + def remove_io_pins(self, pin_name): + # remove io pin in gds, so we could reroute + self.design.remove_layout_pin(pin_name) + + + def replace_layout_pins(self, pin_names): + """ Change the IO pin names with new ones around the perimeter. """ + for pin_name in pin_names: + perimeter_pin_name = pin_name + "_" + "fake" + for pin in self.io_pins_fake: + if pin.name == perimeter_pin_name: + perimeter_pin = pin + self.design.replace_layout_pin(pin_name, perimeter_pin) + break + + + + + diff --git a/compiler/router/router.py b/compiler/router/router.py index 22169cc8c..e9e424523 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -111,6 +111,52 @@ def find_pins(self, pin_name): 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 set and return + """ Find the pins with the given name. """ + debug.info(4, "Finding all pins for {}".format(pin_name)) + + 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): + 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) + + + 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/router_tech.py b/compiler/router/router_tech.py index 7a9fffebb..97cefe092 100644 --- a/compiler/router/router_tech.py +++ b/compiler/router/router_tech.py @@ -89,6 +89,8 @@ def __init__(self, layers, route_track_width): # When we actually create the routes, make them the width of the track (minus 1/2 spacing on each side) self.layer_widths = [self.track_wire, 1, self.track_wire] + # via2 to via3 distance requirements + self.via2_via3_pitch = 0.5 * drc("minwidth_{}".format("via2")) + 0.5 * drc("minwidth_{}".format("via3")) + drc["via3_to_via2"] def same_lpp(self, lpp1, lpp2): """ diff --git a/compiler/router/signal_escape_router.py b/compiler/router/signal_escape_router.py index fe0cca100..b965980c9 100644 --- a/compiler/router/signal_escape_router.py +++ b/compiler/router/signal_escape_router.py @@ -10,14 +10,14 @@ from .graph import graph from .graph_shape import graph_shape from .router import router - +import re class signal_escape_router(router): """ This is the signal escape router that uses the Hanan grid graph method. """ - def __init__(self, layers, design, bbox=None): + def __init__(self, layers, design, bbox=None, mod=0): # `router` is the base router class router.__init__(self, layers, design, bbox) @@ -25,6 +25,17 @@ def __init__(self, layers, design, bbox=None): # New pins are the side supply pins self.new_pins = {} + # Use for add distance of dout pins at the perimeter + self.distance_right = 0 + + self.distance_left = 0 + + # Use for control which edge/position the pins(dout) will be placed + # 0 -> default + # 1 -> all top/bottom + # 2 -> all left/right + self.state_mod = mod + def route(self, pin_names): """ Route the given pins to the perimeter. """ @@ -176,7 +187,6 @@ def add_perimeter_fake_pins(self): layer_name_pp=layer) self.fake_pins.append(pin) - def create_fake_pin(self, pin): """ Create a fake pin on the perimeter orthogonal to the given pin. """ @@ -197,15 +207,48 @@ def create_fake_pin(self, pin): if edge == "top": fake_center = vector(c.x, ur.y + self.track_wire * 2) + # relocate the pin position + pattern = r'^dout' + if re.match(pattern, pin.name): + + if self.state_mod == 0: + pass# do not change, default + + elif self.state_mod == 1: # all top/bottom + if edge == "right": + vertical = False + fake_center = vector(c.x, ll.y - self.track_wire * 2) + self.distance_right += 1 + else: + if edge == "left": + vertical = False + fake_center = vector(c.x, ll.y + self.track_wire * 2) + self.distance_left += 1 + + elif self.state_mod == 2: # all left/right + if (edge == "bottom") or (edge == "right"):# change to the east + vertical = True + fake_center = vector(ur.x + self.track_wire * 2, ll.y + 30 + self.distance_right) + self.distance_right += 1 + else: + if (edge == "top") or (edge == "left"):# change to the west + vertical = True + fake_center = vector(ll.x - self.track_wire * 2, ur.y - 30 - self.distance_left) + self.distance_left += 1 + else: + debug.error("wrong state mod!", -1) + # Create the fake pin shape layer = self.get_layer(int(not vertical)) half_wire_vector = vector([self.half_wire] * 2) nll = fake_center - half_wire_vector nur = fake_center + half_wire_vector + rect = [nll, nur] pin = graph_shape(name="fake", rect=rect, layer_name_pp=layer) + return pin diff --git a/compiler/router/supply_placer.py b/compiler/router/supply_placer.py new file mode 100644 index 000000000..bf72fddee --- /dev/null +++ b/compiler/router/supply_placer.py @@ -0,0 +1,805 @@ +# 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", moat_pins=None): + + # `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 = moat_pins + # store a graphshape of intermediate points(if shift)/source points(if no shift) when connecting moat_pins to outside + # trick: since in the creation of dnwell, these pins are "ordered" added, so they'are also ordered here + # order inside list: left -> right or bottom -> up + self.moat_pins_left = [] + self.moat_pins_right = [] + self.moat_pins_top = [] + self.moat_pins_bottom = [] + # io pins + self.io_pins_left = [] + self.io_pins_right = [] + self.io_pins_top = [] + self.io_pins_bottom = [] + + + def route_outside(self, vdd_name="vdd", gnd_name="gnd", io_pin_names=None): + # only connect supply with inside submodules, not connecting to the power ring + 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.find_pins_inside(vdd_name) + self.find_pins_inside(gnd_name) + self.route_moat(io_pin_names) + # 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)) + + # Prepare the selected moat pins + selected_moat_pins = self.prepare_selected_moat_pins() + # Route vdd and gnd + routed_count = 0 + routed_max = len(self.pins[vdd_name]) + len(self.pins[gnd_name]) + len(self.moat_pins) + len(self.new_pins["gnd"]) + for pin_name in [vdd_name, gnd_name]: + if pin_name == gnd_name: # otherwise will not recognaize the moat blocakge + self.prepare_gds_reader() + # 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() + + pins = self.pins[pin_name] + # Route closest pins according to the minimum spanning tree + for source, target in self.get_mst_with_ring(list(pins), selected_moat_pins, pin_name): + # 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)) + # finsih + self.replace_layout_pins() + + + def route_inside(self, vdd_name="vdd", gnd_name="gnd"): + # only connect supply with inside submodules, not connecting to the power ring + 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.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)) + # finsih + self.replace_layout_pins() + + + def route_moat(self, io_pin_names): + # route the vdd pins at the moat + # io_pin_names is a list + # the moat vdd shape will also be created and stored in the list + for moat_pin in self.moat_pins: + self.check_overlap(moat_pin, io_pin_names) + + + def replace_layout_pins(self): + # clear all the inside "vdd " "gnd" at sram module level + # Copy the pin shape(s) to rectangles + for pin_name in ["vdd", "gnd"]: + # Copy the pin shape(s) to rectangles + for pin in self.design.get_pins(pin_name): + self.design.add_rect(pin.layer, + pin.ll(), + pin.width(), + pin.height()) + + # Remove the pin shape(s) + self.design.remove_layout_pin(pin_name) + + # Get new pins, change the name of ring to extern supply name + # vccd1 ring + pins = self.get_new_pins("vdd") + for pin in pins: + self.design.add_layout_pin(self.ext_vdd_name, + pin.layer, + pin.ll(), + pin.width(), + pin.height()) + # vssd1 ring + pins = self.get_new_pins("gnd") + for pin in pins: + self.design.add_layout_pin(self.ext_gnd_name, + pin.layer, + pin.ll(), + pin.width(), + pin.height()) + + + def prepare_selected_moat_pins(self): + """ Selcet the possibe moat pins, feed into the MST, where will decide which of these pin should be connected to which pin """ + if len(self.design.all_ports) > 1: + # in order to save runtime + # top -> moat pins all + # bottom -> moat pins all + # left -> moat pins near control logic + # right -> moat pins near control logic + # expected connection for control logic + + # for port 0 -> left + start_y = self.design.control_logic_insts[0].by()# bottom edge y value + end_y = self.design.control_logic_insts[0].uy()# up edge y value + # filter the pin in the range + filtered_moat_pins_left = [pin for pin in self.moat_pins_left if start_y <= pin.center().y <= end_y] + + # for port 1 -> right + start_y = self.design.control_logic_insts[1].by()# bottom edge y value + end_y = self.design.control_logic_insts[1].uy()# up edge y value + # filter the pin in the range + filtered_moat_pins_right = [pin for pin in self.moat_pins_right if start_y <= pin.center().y <= end_y] + # return the selected moat pins + selected_moat_pins = [] + selected_moat_pins.extend(filtered_moat_pins_left) + selected_moat_pins.extend(self.moat_pins_bottom) + selected_moat_pins.extend(filtered_moat_pins_right) + selected_moat_pins.extend(self.moat_pins_top) + return selected_moat_pins + + else: # only 1 port + # in order to save runtime + # top -> moat pins all + # bottom -> moat pins all + # left -> moat pins near control logic + # right -> moat pins all + start_y = self.design.control_logic_insts[0].by()# bottom edge y value + end_y = self.design.control_logic_insts[0].uy()# up edge y value + # filter the pin in the range + filtered_moat_pins_left = [pin for pin in self.moat_pins_left if start_y <= pin.center().y <= end_y] + # return the selected moat pins + selected_moat_pins = [] + selected_moat_pins.extend(filtered_moat_pins_left) + selected_moat_pins.extend(self.moat_pins_bottom) + selected_moat_pins.extend(self.moat_pins_right) + selected_moat_pins.extend(self.moat_pins_top) + return selected_moat_pins + + + 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 connect them + 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": + add_distance = self.via2_via3_pitch # if shift, need to fulfill via2-via3 spacing, top/bottom only + pin_too_close = any(abs(io_pin.center().x - source_center.x) < (self.track_width + 0.1) for io_pin in self.io_pins_bottom) + tmp_center = vector(source_center.x, source_center.y) + while pin_too_close: + tmp_center = vector(source_center.x, source_center.y) + add_distance = add_distance + 0.1 + if direction == 1: # right shift + tmp_center = vector((tmp_center.x + add_distance), tmp_center.y) + else: # left shift + tmp_center = vector((tmp_center.x - add_distance), tmp_center.y) + pin_too_close = any(abs(io_pin.center().x - tmp_center.x) < (self.track_width + 0.1) for io_pin in self.io_pins_bottom) + direction = - direction + # the nearst vdd ring + vdd_ring = self.new_pins["vdd"][1] # order in list -> "top", "bottom", "right", "left"] + # bottom ring's y position at it's top + target_egde_y = vdd_ring.center().y + 0.5 * vdd_ring.height() + if tmp_center == source_center: # no overlap + # no jog, direct return the source/target center position + # the target center position, should consider enought space for via + target_point = vector(tmp_center.x, (target_egde_y - 0.5 * self.track_wire)) + source_point = vector(tmp_center.x, tmp_center.y) + point_list = [source_point, target_point] + self.add_wire(point_list, vertical=True) + # store the shape of moat pins, need for route later + ll = vector(source_point.x - 0.5 * self.track_wire, source_point.y - 0.5 * self.track_wire) + ur = vector(source_point.x + 0.5 * self.track_wire, source_point.y + 0.5 * self.track_wire) + rect = [ll, ur] + moat_pin_route = graph_shape("vdd", rect, "m4") + self.moat_pins_bottom.append(moat_pin_route) + else: # need jog + # shift the center + # add rectangle at same layer (original) + intermediate_point = vector(tmp_center.x, tmp_center.y) + source_point = vector(source_center.x, source_center.y) + target_point = vector(tmp_center.x, (target_egde_y - 0.5 * self.track_wire)) + point_list = [source_point, intermediate_point, target_point] + self.add_wire(point_list, vertical=True) + # store the shape of moat pins, need for route later + ll = vector(intermediate_point.x - 0.5 * self.track_wire, intermediate_point.y - 0.5 * self.track_wire) + ur = vector(intermediate_point.x + 0.5 * self.track_wire, intermediate_point.y + 0.5 * self.track_wire) + rect = [ll, ur] + moat_pin_route = graph_shape("vdd", rect, "m4") + self.moat_pins_bottom.append(moat_pin_route) + elif edge == "top": + add_distance = self.via2_via3_pitch # if shift, need to fulfill via2-via3 spacing, top/bottom only + pin_too_close = any(abs(io_pin.center().x - source_center.x) < (self.track_width + 0.1) for io_pin in self.io_pins_top) + tmp_center = vector(source_center.x, source_center.y) + while pin_too_close: + tmp_center = vector(source_center.x, source_center.y) + add_distance = add_distance + 0.1 + if direction == 1: # right shift + tmp_center = vector((tmp_center.x + add_distance), tmp_center.y) + else: # left shift + tmp_center = vector((tmp_center.x - add_distance), tmp_center.y) + pin_too_close = any(abs(io_pin.center().x - tmp_center.x) < (self.track_width + 0.1) for io_pin in self.io_pins_top) + direction = - direction + # the nearst vdd ring + vdd_ring = self.new_pins["vdd"][0] # order in list -> "top", "bottom", "right", "left"] + # top ring's y position at it's bottom + target_egde_y = vdd_ring.center().y - 0.5 * vdd_ring.height() + if tmp_center == source_center: # no overlap + # no jog, direct return the source/target center position + # the target center position, should consider enought space for via + target_point = vector(tmp_center.x, (target_egde_y + 0.5 * self.track_wire)) + source_point = vector(tmp_center.x, tmp_center.y) + point_list = [source_point, target_point] + self.add_wire(point_list, vertical=True) + # store the shape of moat pins, need for route later + ll = vector(source_point.x - 0.5 * self.track_wire, source_point.y - 0.5 * self.track_wire) + ur = vector(source_point.x + 0.5 * self.track_wire, source_point.y + 0.5 * self.track_wire) + rect = [ll, ur] + moat_pin_route = graph_shape("vdd", rect, "m4") + self.moat_pins_top.append(moat_pin_route) + else: # need jog + # shift the center + # add rectangle at same layer (original) + intermediate_point = vector(tmp_center.x, tmp_center.y) + source_point = vector(source_center.x, source_center.y) + target_point = vector(tmp_center.x, (target_egde_y + 0.5 * self.track_wire)) + point_list = [source_point, intermediate_point, target_point] + self.add_wire(point_list, vertical=True) + # store the shape of moat pins, need for route later + ll = vector(intermediate_point.x - 0.5 * self.track_wire, intermediate_point.y - 0.5 * self.track_wire) + ur = vector(intermediate_point.x + 0.5 * self.track_wire, intermediate_point.y + 0.5 * self.track_wire) + rect = [ll, ur] + moat_pin_route = graph_shape("vdd", rect, "m4") + self.moat_pins_top.append(moat_pin_route) + elif edge == "left": + pin_too_close = any(abs(io_pin.center().y - source_center.y) < (self.track_width + 0.1) for io_pin in self.io_pins_left) + tmp_center = vector(source_center.x, source_center.y) + while pin_too_close: + tmp_center = vector(source_center.x, source_center.y) + add_distance = add_distance + 0.1 + if direction == 1: # up shift + tmp_center = vector(tmp_center.x, (tmp_center.y + add_distance)) + else: # down shift + tmp_center = vector(tmp_center.x, (tmp_center.y - add_distance)) + pin_too_close = any(abs(io_pin.center().y - tmp_center.y) < (self.track_width + 0.1) for io_pin in self.io_pins_left) + direction = - direction + # the nearst vdd ring + vdd_ring = self.new_pins["vdd"][3] # order in list -> "top", "bottom", "right", "left"] + # left ring's x position at it's right + target_egde_x = vdd_ring.center().x + 0.5 * vdd_ring.width() + if tmp_center == source_center: # no overlap + # no jog, direct return the source/target center position + # the target center position, should consider enought space for via + target_point = vector((target_egde_x - 0.5 * self.track_wire), tmp_center.y) + source_point = vector(tmp_center.x, tmp_center.y) + point_list = [source_point, target_point] + self.add_wire(point_list, vertical=False) + # store the shape of moat pins, need for route later + ll = vector(source_point.x - 0.5 * self.track_wire, source_point.y - 0.5 * self.track_wire) + ur = vector(source_point.x + 0.5 * self.track_wire, source_point.y + 0.5 * self.track_wire) + rect = [ll, ur] + moat_pin_route = graph_shape("vdd", rect, "m3") + self.moat_pins_left.append(moat_pin_route) + else: # need jog + # shift the center + # add rectangle at same layer (original) + intermediate_point = vector(tmp_center.x, tmp_center.y) + source_point = vector(source_center.x, source_center.y) + target_point = vector((target_egde_x - 0.5 * self.track_wire), tmp_center.y) + point_list = [source_point, intermediate_point, target_point] + self.add_wire(point_list, vertical=False) + # store the shape of moat pins, need for route later + ll = vector(intermediate_point.x - 0.5 * self.track_wire, intermediate_point.y - 0.5 * self.track_wire) + ur = vector(intermediate_point.x + 0.5 * self.track_wire, intermediate_point.y + 0.5 * self.track_wire) + rect = [ll, ur] + moat_pin_route = graph_shape("vdd", rect, "m3") + self.moat_pins_left.append(moat_pin_route) + else: #right + pin_too_close = any(abs(io_pin.center().y - source_center.y) < (self.track_width + 0.1) for io_pin in self.io_pins_right) + tmp_center = vector(source_center.x, source_center.y) + while pin_too_close: + tmp_center = vector(source_center.x, source_center.y) + add_distance = add_distance + 0.1 + if direction == 1: # up shift + tmp_center = vector(tmp_center.x, (tmp_center.y + add_distance)) + else: # down shift + tmp_center = vector(tmp_center.x, (tmp_center.y - add_distance)) + pin_too_close = any(abs(io_pin.center().y - tmp_center.y) < (self.track_width + 0.1) for io_pin in self.io_pins_right) + direction = - direction + # the nearst vdd ring + vdd_ring = self.new_pins["vdd"][2] # order in list -> "top", "bottom", "right", "left"] + # right ring's y position at it's left + target_egde_x = vdd_ring.center().x - 0.5 * vdd_ring.width() + if tmp_center == source_center: # no overlap + # no jog, direct return the source/target center position + # the target center position, should consider enought space for via + target_point = vector((target_egde_x + 0.5 * self.track_wire), tmp_center.y) + source_point = vector(tmp_center.x, tmp_center.y) + point_list = [source_point, target_point] + self.add_wire(point_list, vertical=False) + # store the shape of moat pins, need for route later + ll = vector(source_point.x - 0.5 * self.track_wire, source_point.y - 0.5 * self.track_wire) + ur = vector(source_point.x + 0.5 * self.track_wire, source_point.y + 0.5 * self.track_wire) + rect = [ll, ur] + moat_pin_route = graph_shape("vdd", rect, "m3") + self.moat_pins_right.append(moat_pin_route) + else: # need jog + # shift the center + # add rectangle at same layer (original) + intermediate_point = vector(tmp_center.x, tmp_center.y) + source_point = vector(source_center.x, source_center.y) + target_point = vector((target_egde_x + 0.5 * self.track_wire) ,tmp_center.y) + point_list = [source_point, intermediate_point, target_point] + self.add_wire(point_list, vertical=False) + # store the shape of moat pins, need for route later + ll = vector(intermediate_point.x - 0.5 * self.track_wire, intermediate_point.y - 0.5 * self.track_wire) + ur = vector(intermediate_point.x + 0.5 * self.track_wire, intermediate_point.y + 0.5 * self.track_wire) + rect = [ll, ur] + moat_pin_route = graph_shape("vdd", rect, "m3") + self.moat_pins_right.append(moat_pin_route) + + + def add_wire(self, point_list, vertical=False): + if vertical == True: # m4 line, need start via3(m3 -> m4), end via3(m3 -> m4) + if len(point_list) == 2: # direct connect + # start via + self.add_via(point=point_list[0], + from_layer="m3", + to_layer="m4") + self.add_via(point=point_list[0], + from_layer="m4", + to_layer="m4") # shape + # connection + self.add_line(point_1=point_list[0], + point_2=point_list[1], + layer="m4") + # end via + self.add_via(point=point_list[1], + from_layer="m3", + to_layer="m4") + self.add_via(point=point_list[1], + from_layer="m4", + to_layer="m4") # shape + elif len(point_list) == 3: # need intermediate point + # jog + self.add_line(point_1=point_list[0], + point_2=point_list[1], + layer="m3") + # start_via + self.add_via(point=point_list[1], + from_layer="m3", + to_layer="m4") + self.add_via(point=point_list[1], + from_layer="m3", + to_layer="m3") # shape + # connection + self.add_line(point_1=point_list[1], + point_2=point_list[2], + layer="m4") + # end via + self.add_via(point=point_list[2], + from_layer="m3", + to_layer="m4") + self.add_via(point=point_list[2], + from_layer="m4", + to_layer="m4") # shape + else: # m3 line, need start via2(m2 -> m3), end via3(m3 -> m4) + if len(point_list) == 2: # direct connect + # start via + self.add_via(point=point_list[0], + from_layer="m2", + to_layer="m3") + self.add_via(point=point_list[0], + from_layer="m3", + to_layer="m3") # shape + # connection + self.add_line(point_1=point_list[0], + point_2=point_list[1], + layer="m3") + # end via + self.add_via(point=point_list[1], + from_layer="m3", + to_layer="m4") + self.add_via(point=point_list[1], + from_layer="m3", + to_layer="m3") # shape + elif len(point_list) == 3: # need intermediate point + # jog + self.add_line(point_1=point_list[0], + point_2=point_list[1], + layer="m2") + # start_via + self.add_via(point=point_list[1], + from_layer="m2", + to_layer="m3") + self.add_via(point=point_list[1], + from_layer="m3", + to_layer="m3") # shape + # connection + self.add_line(point_1=point_list[1], + point_2=point_list[2], + layer="m3") + # end via + self.add_via(point=point_list[2], + from_layer="m3", + to_layer="m4") + self.add_via(point=point_list[2], + from_layer="m3", + to_layer="m3") # shape + + + def add_line(self, point_1, point_2, layer="m3"): # "m2", "m3", "m4" + self.design.add_path(layer, [point_1, point_2], self.track_wire) + + + def add_via(self, point, from_layer="m3", to_layer="m4"): + # via could be via2(m2 -> m3), via3(m3 -> m4) + # or a shape at same layer + if from_layer == to_layer: + self.design.add_rect_center(layer=from_layer, + offset=point, + width=self.track_wire, + height=self.track_wire) + else: + self.design.add_via_stack_center(from_layer=from_layer, + to_layer=to_layer, + offset=point) + + + 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 = next(iter(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() + debug.warning("moat pin center -> {0}".format(point)) + # 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 + #debug.warning("pin -> {0}".format(pin)) + 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 == "vdd" + + # 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) + + return pin + + + 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 = 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) + + # 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_mst_with_ring(self, pins, ring_pins, pin_name="vdd"): + """ + Extend the MST logic to connect internal pins to the nearest external ring pins. + """ + # Prepare the pins that are allowed to connect to the moat pins. + # Specical handle gnd ring + candidate_pins = [] + max_distance = 20#13 + if pin_name == "gnd": + ring_pins = [] + ring_pins = self.new_pins[pin_name] + for pin in pins: + # Special handle vdd/gnd in 1port sram(1rw), only at bank top-right is allowed + if len(self.design.all_ports) == 1: + # check if pin is inside bank area + if (self.design.bank_inst.lx() < pin.center().x < self.design.bank_inst.rx()) and (self.design.bank_inst.by() < pin.center().y < self.design.bank_inst.uy()): + # add pin at top-right as candidate, not care the distance + if (pin.center().x > self.design.bank_inst.rx() - 14) and (pin.center().y > self.design.bank_inst.uy() - 14): + candidate_pins.append(pin) + continue + else:# pin in the other area of bank, do not care + continue + # 2 port situation, or the other pins outer bank in 1port situation + for ring_pin in ring_pins: + dist = pin.distance(ring_pin) + if max_distance is None or dist <= max_distance: + candidate_pins.append(pin) + break + + # Compute the MST for internal pins + mst_pairs = self.get_mst_pairs(pins) + + # Connect each internal pin to the nearest external ring pin + used_ring_pins = set() + internal_to_ring_pairs = [] + for pin in candidate_pins: + min_distance = float("inf") + nearest_ring_pin = None + + for ring_pin in ring_pins: + if pin_name == "vdd" and ring_pin in used_ring_pins: + continue + + dist = pin.distance(ring_pin) + if dist < min_distance: + min_distance = dist + nearest_ring_pin = ring_pin + + # Add the connection to the nearest ring pin + if nearest_ring_pin: + internal_to_ring_pairs.append((pin, nearest_ring_pin)) + # Mark the ring pin as used if the pin is VDD + if pin_name == "vdd": + used_ring_pins.add(nearest_ring_pin) + + # Combine internal MST pairs and external connections + full_connections = mst_pairs + internal_to_ring_pairs + + return full_connections + + + 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 fc61f4e64..3e1f431ee 100644 --- a/compiler/router/supply_router.py +++ b/compiler/router/supply_router.py @@ -97,7 +97,7 @@ def add_side_pin(self, pin_name, side, num_vias=3, num_fake_pins=4): 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/compiler/sram.py b/compiler/sram.py index ea71cad4c..9260551b7 100644 --- a/compiler/sram.py +++ b/compiler/sram.py @@ -51,11 +51,83 @@ def __init__(self, sram_config=None, name=None): from openram.modules.sram_1bank import sram_1bank as sram + num_ports = OPTS.num_rw_ports + OPTS.num_r_ports + OPTS.num_w_ports self.s = sram(name, sram_config) - - self.s.create_netlist() - if not OPTS.netlist_only: - self.s.create_layout() + self.s.create_netlist()# not placed & routed jet + + # choose the routung method, maze router or constructive + if OPTS.route_approach == "classic": + cur_state = "IDLE" + if not OPTS.netlist_only: + i = 0 + while i < (OPTS.word_size + 100): + + debug.warning("current state: state = {0}".format(cur_state)) + + if cur_state == "IDLE":# default, fisrt try + try: + self.s.create_layout(position_add=i) + except AssertionError as e: + cur_state = "ALL_TOP_BOTTOM" + del self.s + self.s = sram(name, sram_config) + self.s.create_netlist() + if num_ports > 1: + i = 16 + else: + i = 0 + continue + cur_state = "FINISH" + break + + elif cur_state == "ALL_TOP_BOTTOM": + try: + self.s.create_layout(position_add=i, mod=1) + except AssertionError as e: + cur_state = "ALL_LEFT_RIGHT" + i = OPTS.word_size + 24 + del self.s + self.s = sram(name, sram_config) + self.s.create_netlist() + continue + cur_state = "FINISH" + break + + elif cur_state == "ALL_LEFT_RIGHT": + try: + self.s.create_layout(position_add=i, mod=2) + except AssertionError as e: + cur_state = "ALL_LEFT_RIGHT" + i = i + 1 + if i == (99 + OPTS.word_size):# failed in rounting + debug.error("Failed in rounting", -1) + break + del self.s + self.s = sram(name, sram_config) + self.s.create_netlist() + continue + cur_state = "FINISH" + break + else: + cur_state = "FINISH" + break + elif OPTS.route_approach == "quality": + if not OPTS.netlist_only: + i = 0 + while i < 10: + debug.warning("current i: i = {0}".format(i)) + try: + self.s.create_layout(position_add=i, route_option="quality") + except AssertionError as e: + i = i + 1 + if i == 9: #failed in routing + debug.error("Failed in routing", -1) + break + del self.s + self.s = sram(name, sram_config) + self.s.create_netlist() + continue + break if not OPTS.is_unit_test: print_time("SRAM creation", datetime.datetime.now(), start_time) @@ -110,7 +182,7 @@ def save(self): spname = OPTS.output_path + self.s.name + ".sp" debug.print_raw("SP: Writing to {0}".format(spname)) self.sp_write(spname) - + ''' #comment the following state when generating big sram, and then disable drc/lvs, because maigc_ext stuck # Save a functional simulation file with default period functional(self.s, spname, @@ -132,7 +204,7 @@ def save(self): d.targ_write_ports = [self.s.write_ports[0]] d.write_delay_stimulus() print_time("DELAY", datetime.datetime.now(), start_time) - + ''' #comment the above when generating big sram, and then disable drc/lvs, bevause magic_ext stuck # Save trimmed spice file temp_trim_sp = "{0}trimmed.sp".format(OPTS.output_path) self.sp_write(temp_trim_sp, lvs=False, trim=True) diff --git a/macros/sram_configs/sky130_sram_common.py b/macros/sram_configs/sky130_sram_common.py index 445c88ccb..415e80f24 100644 --- a/macros/sram_configs/sky130_sram_common.py +++ b/macros/sram_configs/sky130_sram_common.py @@ -10,8 +10,10 @@ #local_array_size = 16 route_supplies = "ring" +#supply_pin_type = "top" #route_supplies = "left" -check_lvsdrc = True +#route_supplies = False +check_lvsdrc = False uniquify = True #perimeter_pins = False #netlist_only = True diff --git a/sram_compiler.py b/sram_compiler.py index 34b398bca..989a524ed 100755 --- a/sram_compiler.py +++ b/sram_compiler.py @@ -71,7 +71,6 @@ # Create an SRAM (we can also pass sram_config, see documentation/tutorials for details) from openram import sram s = sram() - # Output the files for the resulting SRAM s.save() diff --git a/technology/sky130/tech/tech.py b/technology/sky130/tech/tech.py index 0df22cb7d..b52a35b3b 100755 --- a/technology/sky130/tech/tech.py +++ b/technology/sky130/tech/tech.py @@ -502,7 +502,8 @@ drc.add_layer("nwell", width=0.840, spacing=1.270) - +# nwell.6 Minimum enclosure of nwell hole by deep nwell outside UHVI +drc["minclosure_nwell_by_dnwell"] = 1.030 # poly.1a Minimum width of poly # poly.2 Minimum spacing of poly AND active drc.add_layer("poly", @@ -662,7 +663,8 @@ drc.add_layer("via3", width=0.200, spacing=0.200) - +# via3.12 Minimum spacing of via3 to via2 (cu) +drc["via3_to_via2"] = 0.180 # m4.1 Minimum width of metal4 # m4.2 Minimum spacing of metal4 # m4.7 Minimum area of metal4 @@ -748,6 +750,7 @@ spice["inv_leakage"] = 1 # Leakage power of inverter in nW spice["nand2_leakage"] = 1 # Leakage power of 2-input nand in nW spice["nand3_leakage"] = 1 # Leakage power of 3-input nand in nW +spice["nand4_leakage"] = 1 # Leakage power of 4-input nand in nW spice["nor2_leakage"] = 1 # Leakage power of 2-input nor in nW spice["dff_leakage"] = 1 # Leakage power of flop in nW