# 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]) 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 prepare_escape_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) # prepare pins for every instances for pin_name in ["vdd"]: count =0 for pin in self.design.control_logic_insts[0].get_pins(pin_name): debug.warning("vdd pin shape -> ll:{0} ur:{1}".format(pin.ll(), pin.ur())) """ count = count + 1 new_pin = graph_shape("gnd", pin.rect, pin.layer) source = new_pin source_center_x = pin.center().x target_ur_y = self.new_pins[self.ext_gnd_name][1].center().y + 0.5 * self.new_pins[self.ext_gnd_name][1].height() ll = vector(source_center_x - 2.6 - 0.5 * self.track_wire, pin.center().y - 0.5 * self.track_wire)#target_ur_y - self.track_wire) ur = vector(source_center_x - 2.6 + 0.5 * self.track_wire, pin.center().y + 0.5 * self.track_wire)#target_ur_y) target = graph_shape("fake", [ll,ur], pin.layer) # 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) """ debug.warning("vdd of wmask number -> {0}".format(count)) debug.warning("instance postion -> {0} {1}".format(self.design.control_logic_insts[0].lx(), self.design.control_logic_insts[0].by())) # print pins_all for pin in self.all_pins: debug.warning("all_pins -> {0}".format(pin)) 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 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 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 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 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 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 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 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 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 = 13 if pin_name == "gnd": ring_pins = [] ring_pins = self.new_pins[pin_name] for pin in pins: 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]