# See LICENSE for licensing information. # # Copyright (c) 2016-2019 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. # import collections import debug from tech import drc from vector import vector import design 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 vcg_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 vcg_nets_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.vcg_pin_overlap(pin1, pin2, pitch): return True return False def hcg_nets_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.design): unique_id = 0 def __init__(self, netlist, offset, layer_stack, directions=None, vertical=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 design.design.__init__(self, name) self.netlist = netlist self.offset = offset self.layer_stack = layer_stack self.directions = directions self.vertical = vertical 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 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.hcg_nets_overlap(net2): try: hcg[net1.name].append(net2.name) except KeyError: hcg[net1.name] = [net2.name] try: hcg[net2.name].append(net1.name) except KeyError: hcg[net2.name] = [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] = [] for net1 in nets: for net2 in nets: # Skip yourself if net1.name == net2.name: continue if net1.vcg_nets_overlap(net2, pitch): vcg[net2.name].append(net1.name) current_offset = self.offset # 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.vertical_nonpref_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.horizontal_nonpref_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 = self.offset.y if self.vertical else self.offset.x if current_offset_value == initial_offset_value: # FIXME: We don't support cyclic VCGs right now. 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, self.offset.y) else: current_offset = vector(self.offset.x, current_offset.y + self.vertical_nonpref_pitch) # Return the size of the channel if self.vertical: self.width = 0 self.height = current_offset.y return current_offset.y + self.vertical_nonpref_pitch - self.offset.y else: self.width = current_offset.x self.height = 0 return current_offset.x + self.horizontal_nonpref_pitch - self.offset.x 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 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)