# See LICENSE for licensing information. # # Copyright (c) 2016-2024 Regents of the University of California and The Board # of Regents for the Oklahoma Agricultural and Mechanical College # (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): self.name = net_name self.pins = pins self.vertical = vertical # Keep track of the internval if vertical: self.min_value = min(i.by() for i in pins) self.max_value = max(i.uy() for i in pins) else: self.min_value = min(i.lx() for i in pins) self.max_value = max(i.rx() for i in pins) # Keep track of the conflicts self.conflicts = [] def __str__(self): return self.name def __repr__(self): return self.name def __lt__(self, other): return self.min_value < other.min_value def pin_overlap(self, pin1, pin2, pitch): """ Check for vertical or horizontal overlap of the two pins """ # FIXME: If the pins are not in a row, this may break. # However, a top pin shouldn't overlap another top pin, # for example, so the extra comparison *shouldn't* matter. # Pin 1 must be in the "BOTTOM" set x_overlap = pin1.by() < pin2.by() and abs(pin1.center().x - pin2.center().x) < pitch # Pin 1 must be in the "LEFT" set y_overlap = pin1.lx() < pin2.lx() and abs(pin1.center().y - pin2.center().y) < pitch overlaps = (not self.vertical and x_overlap) or (self.vertical and y_overlap) return overlaps def pins_overlap(self, other, pitch): """ Check all the pin pairs on two nets and return a pin overlap if any pin overlaps. """ for pin1 in self.pins: for pin2 in other.pins: if self.pin_overlap(pin1, pin2, pitch): return True return False def segment_overlap(self, other): """ Check if the horizontal span of the two nets overlaps eachother. """ min_overlap = self.min_value >= other.min_value and self.min_value <= other.max_value max_overlap = self.max_value >= other.min_value and self.max_value <= other.max_value return min_overlap or max_overlap class channel_route(design): unique_id = 0 def __init__(self, netlist, offset, layer_stack, directions=None, vertical=False, 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 routing channel will start. This does NOT try to minimize the number of tracks -- instead, it picks an order to avoid the vertical conflicts between pins. The track size must be the number of nets times the *nonpreferred* routing of the non-track layer pitch. """ name = "cr_{0}".format(channel_route.unique_id) channel_route.unique_id += 1 super().__init__(name) self.netlist = netlist self.offset = offset self.layer_stack = layer_stack self.directions = directions 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 if self.get_preferred_direction(layer_stack[0]) == "V": self.vertical_layer = layer_stack[0] self.horizontal_layer = layer_stack[2] else: self.vertical_layer = layer_stack[2] self.horizontal_layer = layer_stack[0] elif directions == "nonpref": # Use the preferred layer directions if self.get_preferred_direction(layer_stack[0]) == "V": self.vertical_layer = layer_stack[2] self.horizontal_layer = layer_stack[0] else: self.vertical_layer = layer_stack[0] self.horizontal_layer = layer_stack[2] else: # Use the layer directions specified to the router rather than # the preferred directions debug.check(directions[0] != directions[1], "Must have unique layer directions.") if directions[0] == "V": self.vertical_layer = layer_stack[0] self.horizontal_layer = layer_stack[2] else: self.horizontal_layer = layer_stack[0] self.vertical_layer = layer_stack[2] layer_stuff = self.get_layer_pitch(self.vertical_layer) (self.vertical_nonpref_pitch, self.vertical_pitch, self.vertical_width, self.vertical_space) = layer_stuff 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() def remove_net_from_graph(self, pin, g): """ Remove the pin from the graph and all conflicts """ g.pop(pin, None) # Remove the pin from all conflicts # FIXME: This is O(n^2), so maybe optimize it. for other_pin, conflicts in g.items(): if pin in conflicts: g[other_pin].remove(pin) return g def route(self): # Create names for the nets for the graphs nets = [] index = 0 # print(self.netlist) for pin_list in self.netlist: nets.append(channel_net("n{}".format(index), pin_list, self.vertical)) index += 1 # Create the (undirected) horizontal constraint graph hcg = collections.OrderedDict() for net1 in nets: for net2 in nets: if net1.name == net2.name: continue if net1.segment_overlap(net2): try: hcg[net1.name].add(net2.name) except KeyError: hcg[net1.name] = set([net2.name]) try: hcg[net2.name].add(net1.name) except KeyError: hcg[net2.name] = set([net1.name]) # Initialize the vertical conflict graph (vcg) # and make a list of all pins vcg = collections.OrderedDict() # print("Nets:") # for net_name in nets: # print(net_name, [x.name for x in nets[net_name]]) # Find the vertical pin conflicts # FIXME: O(n^2) but who cares for now if self.vertical: pitch = self.horizontal_nonpref_pitch else: pitch = self.vertical_nonpref_pitch for net in nets: vcg[net.name] = set() for net1 in nets: for net2 in nets: # Skip yourself if net1.name == net2.name: continue if net1.pins_overlap(net2, pitch): vcg[net2.name].add(net1.name) # Check if there are any cycles net1 <---> net2 in the VCG # Some of the pins may be to the left/below the channel offset, # so adjust if this is the case self.min_value = min([n.min_value for n in nets]) self.max_value = min([n.max_value for n in nets]) if self.vertical: real_channel_offset = vector(self.offset.x, min(self.min_value, self.offset.y)) 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: current_offset_value = current_offset.y if self.vertical else current_offset.x # from pprint import pformat # print("VCG:\n", pformat(vcg)) # for name,net in vcg.items(): # print(name, net.min_value, net.max_value, net.conflicts) # print(current_offset) # get a route from conflict graph with empty fanout set for net in nets: # If it has no conflicts and the interval is to the right of the current offset in the track if net.min_value >= current_offset_value and len(vcg[net.name]) == 0: # print("Routing {}".format(net.name)) # Add the trunk routes from the bottom up for # horizontal or the left to right for vertical if self.vertical: self.add_vertical_trunk_route(net.pins, current_offset, self.horizontal_pitch) current_offset = vector(current_offset.x, net.max_value + self.horizontal_nonpref_pitch) else: 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 vcg = self.remove_net_from_graph(net.name, vcg) nets.remove(net) break else: # If we made a full pass and the offset didn't change... current_offset_value = current_offset.y if self.vertical else current_offset.x initial_offset_value = real_channel_offset.y if self.vertical else real_channel_offset.x if current_offset_value == initial_offset_value: debug.info(0, "Channel offset: {}".format(real_channel_offset)) debug.info(0, "Current offset: {}".format(current_offset)) debug.info(0, "VCG {}".format(str(vcg))) debug.info(0, "HCG {}".format(str(hcg))) for net in nets: debug.info(0, "{0} pin: {1}".format(net.name, str(net.pins))) if self.parent: debug.info(0, "Saving vcg.gds") self.parent.gds_write("vcg.gds") debug.error("Cyclic VCG in channel router.", -1) # Increment the track and reset the offset to the start (like a typewriter) if self.vertical: current_offset = vector(current_offset.x + self.horizontal_nonpref_pitch, real_channel_offset.y) else: current_offset = vector(real_channel_offset.x, current_offset.y + self.vertical_nonpref_pitch) # Return the size of the channel if self.vertical: self.width = current_offset.x + self.horizontal_nonpref_pitch - self.offset.x self.height = self.max_value + self.vertical_nonpref_pitch - self.offset.y else: self.width = self.max_value + self.horizontal_nonpref_pitch - self.offset.x self.height = current_offset.y + self.vertical_nonpref_pitch - self.offset.y def get_layer_pitch(self, layer): """ Return the track pitch on a given layer """ try: # FIXME: Using non-pref pitch here due to overlap bug in VCG constraints. # It should just result in inefficient channel width but will work. pitch = getattr(self, "{}_pitch".format(layer)) nonpref_pitch = getattr(self, "{}_nonpref_pitch".format(layer)) space = getattr(self, "{}_space".format(layer)) except AttributeError: 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, pitch): """ Create a trunk route for all pins with the trunk located at the given y offset. """ max_x = max([pin.center().x for pin in pins]) min_x = min([pin.center().x for pin in pins]) # if we are less than a pitch, just create a non-preferred layer jog non_preferred_route = max_x - min_x <= pitch if non_preferred_route: half_layer_width = 0.5 * drc["minwidth_{0}".format(self.vertical_layer)] # 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.uc() else: 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]) 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: pin_pos = pin.uc() else: pin_pos = pin.bc() mid = vector(pin_pos.x, trunk_offset.y) self.add_path(self.vertical_layer, [pin_pos, mid]) if not non_preferred_route: 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_vertical_trunk_route(self, pins, trunk_offset, pitch): """ Create a trunk route for all pins with the trunk located at the given x offset. """ max_y = max([pin.center().y for pin in pins]) min_y = min([pin.center().y for pin in pins]) # if we are less than a pitch, just create a non-preferred layer jog non_preferred_route = max_y - min_y <= pitch if non_preferred_route: half_layer_width = 0.5 * drc["minwidth_{0}".format(self.horizontal_layer)] # Add the vertical trunk on the horizontal layer! self.add_path(self.horizontal_layer, [vector(trunk_offset.x, min_y - half_layer_width), vector(trunk_offset.x, max_y + half_layer_width)]) # Route each pin to the trunk for pin in pins: # Find the correct side of the pin if pin.cx() < trunk_offset.x: pin_pos = pin.rc() else: pin_pos = pin.lc() # No bend needed here mid = vector(trunk_offset.x, pin_pos.y) self.add_path(self.horizontal_layer, [pin_pos, mid]) else: # Add the vertical trunk self.add_path(self.vertical_layer, [vector(trunk_offset.x, min_y), vector(trunk_offset.x, max_y)]) # Route each pin to the trunk for pin in pins: # Find the correct side of the pin if pin.cx() < trunk_offset.x: pin_pos = pin.rc() else: pin_pos = pin.lc() mid = vector(trunk_offset.x, pin_pos.y) self.add_path(self.horizontal_layer, [pin_pos, mid]) if not non_preferred_route: 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.horizontal_layer, offset=pin_pos)