From b9479899707a94711d03d679ac082acb78783e5c Mon Sep 17 00:00:00 2001 From: Matt Guthaus Date: Wed, 16 Nov 2016 15:02:07 -0800 Subject: [PATCH] Add router data structure, blockage parser, pin parser, initial unit tests --- compiler/gdsMill/gdsMill/vlsiLayout.py | 38 +-- compiler/router/cell.py | 33 +++ compiler/router/grid.py | 68 ++++++ compiler/router/router.py | 227 +++++++++++++++--- compiler/router/tests/01_no_blockages_test.py | 54 +++++ compiler/router/tests/A_to_B_m1_only.gds | Bin 0 -> 2048 bytes .../router/tests/A_to_B_m1m2_blockages.gds | Bin 0 -> 2048 bytes .../tests/A_to_B_m1m2_diff_layer_pins.gds | Bin 0 -> 4096 bytes .../tests/A_to_B_m1m2_same_layer_pins.gds | Bin 0 -> 4096 bytes compiler/router/tests/A_to_B_no_blockages.gds | Bin 0 -> 2048 bytes compiler/router/tests/config_freepdk45.py | 23 ++ compiler/router/tests/config_scn3me_subm.py | 23 ++ compiler/router/tests/regress.py | 30 +++ compiler/vector.py | 33 ++- 14 files changed, 483 insertions(+), 46 deletions(-) create mode 100644 compiler/router/cell.py create mode 100644 compiler/router/grid.py create mode 100644 compiler/router/tests/01_no_blockages_test.py create mode 100644 compiler/router/tests/A_to_B_m1_only.gds create mode 100644 compiler/router/tests/A_to_B_m1m2_blockages.gds create mode 100644 compiler/router/tests/A_to_B_m1m2_diff_layer_pins.gds create mode 100644 compiler/router/tests/A_to_B_m1m2_same_layer_pins.gds create mode 100644 compiler/router/tests/A_to_B_no_blockages.gds create mode 100644 compiler/router/tests/config_freepdk45.py create mode 100644 compiler/router/tests/config_scn3me_subm.py create mode 100644 compiler/router/tests/regress.py diff --git a/compiler/gdsMill/gdsMill/vlsiLayout.py b/compiler/gdsMill/gdsMill/vlsiLayout.py index 172e0f1c..a7e24cac 100644 --- a/compiler/gdsMill/gdsMill/vlsiLayout.py +++ b/compiler/gdsMill/gdsMill/vlsiLayout.py @@ -602,9 +602,9 @@ class VlsiLayout: for boundary in self.structures[self.rootStructureName].boundaries: if boundary.drawingLayer==borderlayer: debug.info(debug_level,"Find border "+str(boundary.coordinates)) - left_button=boundary.coordinates[0] + left_bottom=boundary.coordinates[0] right_top=boundary.coordinates[2] - cellSize=[right_top[0]-left_button[0],right_top[1]-left_button[1]] + cellSize=[right_top[0]-left_bottom[0],right_top[1]-left_bottom[1]] cellSizeMicron=[cellSize[0]*self.units[0],cellSize[1]*self.units[0]] if not(cellSizeMicron): debug.error("Error: "+str(self.rootStructureName)+".cell_size information not found yet") @@ -620,6 +620,15 @@ class VlsiLayout: cellSizeMicron=[cellSize[0]*self.units[0],cellSize[1]*self.units[0]] return cellSizeMicron + def measureBoundary(self,startStructure): + self.rootStructureName=startStructure + self.populateCoordinateMap() + cellBoundary = [None, None, None, None] + for TreeUnit in self.xyTree: + cellBoundary=self.measureSizeInStruture(TreeUnit,cellBoundary) + return [[self.units[0]*cellBoundary[0],self.units[0]*cellBoundary[1]], + [self.units[0]*cellBoundary[2],self.units[0]*cellBoundary[3]]] + def measureSizeInStruture(self,Struture,cellBoundary): StrutureName=Struture[0] StrutureOrgin=[Struture[1][0],Struture[1][1]] @@ -631,9 +640,9 @@ class VlsiLayout: debug.info(debug_level,"-Structure direction: vVector["+str(StruturevVector)+"]") for boundary in self.structures[str(StrutureName)].boundaries: - left_button=boundary.coordinates[0] + left_bottom=boundary.coordinates[0] right_top=boundary.coordinates[2] - thisBoundary=[left_button[0],left_button[1],right_top[0],right_top[1]] + thisBoundary=[left_bottom[0],left_bottom[1],right_top[0],right_top[1]] thisBoundary=self.tranformRectangle(thisBoundary,StrutureuVector,StruturevVector) thisBoundary=[thisBoundary[0]+StrutureOrgin[0],thisBoundary[1]+StrutureOrgin[1], thisBoundary[2]+StrutureOrgin[0],thisBoundary[3]+StrutureOrgin[1]] @@ -670,7 +679,8 @@ class VlsiLayout: pin_boundary=self.readPinInStructureList(label_coordinate, label_layer) debug.info(debug_level, "Find pin covers "+str(label_name)+" at "+str(pin_boundary)) - pin_boundary=[pin_boundary[0]*self.units[0],pin_boundary[1]*self.units[0],pin_boundary[2]*self.units[0],pin_boundary[3]*self.units[0]] + pin_boundary=[pin_boundary[0]*self.units[0],pin_boundary[1]*self.units[0], + pin_boundary[2]*self.units[0],pin_boundary[3]*self.units[0]] return [label_name, label_layer, pin_boundary] def readPinInStructureList(self,label_coordinates,layer): @@ -692,9 +702,9 @@ class VlsiLayout: for boundary in self.structures[str(StrutureName)].boundaries: if layer==boundary.drawingLayer: - left_button=boundary.coordinates[0] + left_bottom=boundary.coordinates[0] right_top=boundary.coordinates[2] - MetalBoundary=[left_button[0],left_button[1],right_top[0],right_top[1]] + MetalBoundary=[left_bottom[0],left_bottom[1],right_top[0],right_top[1]] MetalBoundary=self.tranformRectangle(MetalBoundary,StrutureuVector,StruturevVector) MetalBoundary=[MetalBoundary[0]+StrutureOrgin[0],MetalBoundary[1]+StrutureOrgin[1], MetalBoundary[2]+StrutureOrgin[0],MetalBoundary[3]+StrutureOrgin[1]] @@ -707,18 +717,18 @@ class VlsiLayout: return label_boundary def tranformRectangle(self,orignalRectangle,uVector,vVector): - LeftButton=mpmath.matrix([orignalRectangle[0],orignalRectangle[1]]) - LeftButton=self.tranformCoordinate(LeftButton,uVector,vVector) + LeftBottom=mpmath.matrix([orignalRectangle[0],orignalRectangle[1]]) + LeftBottom=self.tranformCoordinate(LeftBottom,uVector,vVector) RightUp=mpmath.matrix([orignalRectangle[2],orignalRectangle[3]]) RightUp=self.tranformCoordinate(RightUp,uVector,vVector) - Left=min(LeftButton[0],RightUp[0]) - Button=min(LeftButton[1],RightUp[1]) - Right=max(LeftButton[0],RightUp[0]) - Up=max(LeftButton[1],RightUp[1]) + Left=min(LeftBottom[0],RightUp[0]) + Bottom=min(LeftBottom[1],RightUp[1]) + Right=max(LeftBottom[0],RightUp[0]) + Up=max(LeftBottom[1],RightUp[1]) - return [Left,Button,Right,Up] + return [Left,Bottom,Right,Up] def tranformCoordinate(self,Coordinate,uVector,vVector): x=Coordinate[0]*uVector[0]+Coordinate[1]*uVector[1] diff --git a/compiler/router/cell.py b/compiler/router/cell.py new file mode 100644 index 00000000..7b93e116 --- /dev/null +++ b/compiler/router/cell.py @@ -0,0 +1,33 @@ +from PIL import ImageColor + +class cell: + """ + A single cell that can be occupied in a given layer, blocked, + visited, etc. + """ + scale=1 + + def __init__(self): + self.visited = 0 + + self.blocked = False + + self.is_source = False + self.is_target = False + + + def get_color(self): + + # Blues are horizontal + if self.blocked: + return ImageColor.getrgb("Blue") + # Reds are source/sink + if self.is_source or self.is_target: + return ImageColor.getrgb("Red") + + if self.visited>0: + return [255-min(int(self.visited/cell.scale * 255),255)] * 3 + + return [255,255,255] + + diff --git a/compiler/router/grid.py b/compiler/router/grid.py new file mode 100644 index 00000000..53dca715 --- /dev/null +++ b/compiler/router/grid.py @@ -0,0 +1,68 @@ +import numpy as np +from PIL import Image +import debug + +from cell import cell + +class grid: + """A two layer routing map. Each cell can be blocked in the vertical + or horizontal layer. + + """ + + def __init__(self, width, height): + """ Create a routing map of width x height cells and 2 in the z-axis. """ + self.width=width + self.height=height + self.map={} + for x in range(width): + for y in range(height): + for z in range(2): + self.map[x,y,z]=cell() + + def view(self,): + """ + View the data by creating an RGB array and mapping the data + structure to the RGB color palette. + """ + + v_map = np.zeros((self.width,self.height,3), 'uint8') + mid_map = np.ones((25,self.height,3), 'uint8') + h_map = np.ones((self.width,self.height,3), 'uint8') + + # We shouldn't have a path greater than 50% the HPWL + # so scale all visited indices by this value for colorization + cell.scale = 1.5 * (self.width+self.height) + for x in range(self.width): + for y in range(self.height): + h_map[x,y] = self.map[x,y,0].get_color() + v_map[x,y] = self.map[x,y,1].get_color() + + v_img = Image.fromarray(v_map, 'RGB').rotate(90) + mid_img = Image.fromarray(mid_map, 'RGB').rotate(90) + h_img = Image.fromarray(h_map, 'RGB').rotate(90) + + # concatenate them into a plot with the two layers + img = Image.new('RGB', (2*self.width+25, self.height)) + img.paste(h_img, (0,0)) + img.paste(mid_img, (self.width,0)) + img.paste(v_img, (self.width+25,0)) + img.show() + + def set_property(self,ll,ur,z,name,value=True): + for x in range(int(ll[0]),int(ur[0])): + for y in range(int(ll[1]),int(ur[1])): + setattr (self.map[x,y,z], name, True) + + def add_blockage(self,ll,ur,z): + debug.info(1,"Adding blockage ll={0} ur={1} z={2}".format(str(ll),str(ur),z)) + self.set_property(ll,ur,z,"blocked") + + def set_source(self,ll,ur,z): + debug.info(1,"Adding source ll={0} ur={1} z={2}".format(str(ll),str(ur),z)) + self.set_property(ll,ur,z,"is_source") + + def set_target(self,ll,ur,z): + debug.info(1,"Adding target ll={0} ur={1} z={2}".format(str(ll),str(ur),z)) + self.set_property(ll,ur,z,"is_target") + diff --git a/compiler/router/router.py b/compiler/router/router.py index c41de2e2..6b4eef20 100644 --- a/compiler/router/router.py +++ b/compiler/router/router.py @@ -1,40 +1,120 @@ import gdsMill import tech +from contact import contact import math import debug -from collections import defaultdict +from vector import vector +import grid + class router: """A router class to read an obstruction map from a gds and plan a - route on a given layer. + route on a given layer. This is limited to two layer routes. """ - def __init__(self, gdsName, topName, layers): + def __init__(self, gds_name): """Use the gds file for the blockages with the top module topName and layers for the layers to route on """ - self.topName = topName - self.gdsName = gdsName - self.layout = gdsMill.VlsiLayout(units=tech.GDS["unit"]) + self.gds_name = gds_name + self.layout = gdsMill.VlsiLayout() self.reader = gdsMill.Gds2reader(self.layout) - self.reader.loadFromFile(gdsName) + self.reader.loadFromFile(gds_name) + self.top_name = self.layout.rootStructureName self.unit = float(self.layout.info['units'][0]) - self.layers = layers + print "Units:",self.unit - self.find_blockages() - def create_map(self): - pass + self.pin_names = [] + self.pin_shapes = {} + self.pin_layers = {} + + self.boundary = self.layout.measureBoundary(self.top_name) + self.ll = vector(self.boundary[0]) + self.ur = vector(self.boundary[1]) + self.size = self.ur - self.ll + self.width = self.size.x + self.height = self.size.y + + print "Boundary: ",self.boundary + print "Size: ", self.width,self.height + + # to scale coordinates by units + self.unit_factor = [self.unit] * 2 + + # We will offset so ll is at (0,0) + self.offset = self.ll + print "Offset: ",self.offset + + + def set_top(self,top_name): + """ If we want to route something besides the top-level cell.""" + self.top_name = top_name + + + def set_layers(self, layers): + """ Allows us to change the layers that we are routing on. """ + self.layers = layers + (horiz_layer, via_layer, vert_layer) = self.layers + if (via_layer != None): + self.via_layer_name = via_layer + else: + self.via_layer_name = None + + self.vert_layer_name = vert_layer + self.vert_layer_width = tech.drc["minwidth_{0}".format(vert_layer)] + self.vert_layer_number = tech.layer[vert_layer] + + self.horiz_layer_name = horiz_layer + self.horiz_layer_width = tech.drc["minwidth_{0}".format(horiz_layer)] + self.horiz_layer_number = tech.layer[horiz_layer] + + # contacted track spacing + via_connect = contact(self.layers, (1, 1)) + self.horiz_track_width = tech.drc[str(self.horiz_layer_name)+"_to_"+str(self.horiz_layer_name)] + via_connect.width + self.vert_track_width = tech.drc[str(self.vert_layer_name)+"_to_"+str(self.vert_layer_name)] + via_connect.width + + # This is so we can use a single resolution grid for both layers + self.track_width = max(self.horiz_track_width,self.vert_track_width) + print "Track width:",self.track_width + + # to scale coordinates to tracks + self.track_factor = [1/self.track_width] * 2 + + + + def create_routing_grid(self): + """ Create a routing grid that spans given area. Wires cannot exist outside region. """ + + self.width_in_tracks = int(math.ceil(self.width/self.track_width)) + self.height_in_tracks = int(math.ceil(self.height/self.track_width)) + + print "Size (in tracks): ", self.width_in_tracks, self.height_in_tracks + + self.rg = grid.grid(self.width_in_tracks,self.height_in_tracks) + + + def find_pin(self,pin): + """ Finds the offsets to the gds pins """ + (pin_name,pin_layer,pin_shape) = self.layout.readPin(str(pin)) + # repack the shape as a pair of vectors rather than four values + new_shape = self.convert_to_tracks([vector(pin_shape[0],pin_shape[1]),vector(pin_shape[2],pin_shape[3])]) + self.pin_names.append(pin_name) + self.pin_shapes[str(pin)] = new_shape + self.pin_layers[str(pin)] = pin_layer + return new_shape def find_blockages(self): - for layer in self.layer: - debug.info("Layer: " + layer) - self.writeObstruct(self.topName, layer) + if len(self.pin_names)!=2: + debug.error("Must set pins before creating blockages.",-1) + + for layer in self.layers: + self.write_obstacle(self.top_name) + def add_route(self,start, end, layerstack): """ Add a wire route from the start to the end point""" - pass def create_steiner_routes(self,pins): @@ -46,30 +126,115 @@ class router: """ Find the set of steiner points and return them.""" pass + def translate_coordinates(self, coord, mirr, angle, xyShift): + """Calculate coordinates after flip, rotate, and shift""" + coordinate = [] + for item in coord: + x = (item[0]*math.cos(angle)-item[1]*mirr*math.sin(angle)+xyShift[0]) + y = (item[0]*math.sin(angle)+item[1]*mirr*math.cos(angle)+xyShift[1]) + coordinate += [(x, y)] + return coordinate + + def min_max_coord(self, coordTrans): + """Find the lowest and highest conner of a Rectangle""" + coordinate = [] + minx = min(coordTrans[0][0], coordTrans[1][0], coordTrans[2][0], coordTrans[3][0]) + maxx = max(coordTrans[0][0], coordTrans[1][0], coordTrans[2][0], coordTrans[3][0]) + miny = min(coordTrans[0][1], coordTrans[1][1], coordTrans[2][1], coordTrans[3][1]) + maxy = max(coordTrans[0][1], coordTrans[1][1], coordTrans[2][1], coordTrans[3][1]) + coordinate += [vector(minx, miny)] + coordinate += [vector(maxx, maxy)] + return coordinate + + def set_source(self,name): + shape = self.find_pin(name) + zindex = 0 if self.pin_layers[name]==self.horiz_layer_number else 1 + debug.info(0,"Set source: " + str(name) + " " + str(shape) + " z=" + str(zindex)) + self.rg.set_source(shape[0],shape[1],zindex) + + def set_target(self,name): + shape = self.find_pin(name) + zindex = 0 if self.pin_layers[name]==self.horiz_layer_number else 1 + debug.info(0,"Set target: " + str(name) + " " + str(shape) + " z=" + str(zindex)) + self.rg.set_target(shape[0],shape[1],zindex) - def writeObstruct(self, sr, lay, mirr = 1, angle = math.radians(float(0)), xyShift = (0, 0)): + def write_obstacle(self, sref, mirr = 1, angle = math.radians(float(0)), xyShift = (0, 0)): """Recursive write boundaries on each Structure in GDS file to LEF""" - for boundary in self.layout.structures[sr].boundaries: - coordTrans = self.coordinatesTranslate(boundary.coordinates, mirr, angle, xyShift) - rect = self.minMaxCoord(coordTrans) - lay_convert = tech.layer[lay] - if boundary.drawingLayer == lay_convert: - text = " RECT " - for item in rect: - text += " {0} {1}".format(item[0]*self.unit, item[1]*self.unit) - debug.info(text) - - for sref in self.layout.structures[sr].srefs: + + for boundary in self.layout.structures[sref].boundaries: + coordTrans = self.translate_coordinates(boundary.coordinates, mirr, angle, xyShift) + shape = self.min_max_coord(coordTrans) + + if boundary.drawingLayer in [self.vert_layer_number,self.horiz_layer_number]: + ll_microns=shape[0].scale(self.unit_factor) + ur_microns=shape[1].scale(self.unit_factor) + + shape_tracks=self.convert_to_tracks([ll_microns,ur_microns]) + + if shape_tracks not in self.pin_shapes.values(): + # inflate the ll and ur by 1 track in each direction + [ll,ur]=shape_tracks + ll = vector(0,0).max(ll + vector(-1,-1)) + ur = vector(self.width_in_tracks-1,self.height_in_tracks-1).min(ur + vector(1,1)) + zlayer = 0 if boundary.drawingLayer==self.horiz_layer_number else 1 + debug.info(1,"Blockage: "+str([ll,ur])+" z="+str(zlayer)) + self.rg.add_blockage(ll,ur,zlayer) + else: + debug.info(2,"Skip: "+str(shape_tracks)) + + + + # recurse given the mirror, angle, etc. + for cur_sref in self.layout.structures[sref].srefs: sMirr = 1 if sref.transFlags[0] == True: sMirr = -1 sAngle = math.radians(float(0)) if sref.rotateAngle: - sAngle = math.radians(float(sref.rotateAngle)) + sAngle = math.radians(float(cur_sref.rotateAngle)) sAngle += angle - x = sref.coordinates[0] - y = sref.coordinates[1] + x = cur_sref.coordinates[0] + y = cur_sref.coordinates[1] newX = (x)*math.cos(angle) - mirr*(y)*math.sin(angle) + xyShift[0] newY = (x)*math.sin(angle) + mirr*(y)*math.cos(angle) + xyShift[1] sxyShift = (newX, newY) - self.writeObstruct(sref.sName, lay,sMirr, sAngle, sxyShift) + + self.write_obstacle(cur_sref.sName, layer,sMirr, sAngle, sxyShift) + + def inflate_obstacle(self,shape): + # TODO: inflate by the layer design rules + return shape + + def convert_to_tracks(self,shape): + """ + Convert a rectangular shape into track units. + """ + [ll,ur] = shape + + # fix offset + ll = snap_to_grid(ll-self.offset) + ur = snap_to_grid(ur-self.offset) + + # always round down, because we will add a track + # to inflate each object later + ll = ll.scale(self.track_factor).ceil() + ur = ur.scale(self.track_factor).floor() + + return [ll,ur] + + +# FIXME: This should be replaced with vector.snap_to_grid at some point +def snap_to_grid(offset): + """ + Changes the coodrinate to match the grid settings + """ + grid = tech.drc["grid"] + x = offset[0] + y = offset[1] + # this gets the nearest integer value + xgrid = int(round(round((x / grid), 2), 0)) + ygrid = int(round(round((y / grid), 2), 0)) + xoff = xgrid * grid + yoff = ygrid * grid + return vector(xoff, yoff) + diff --git a/compiler/router/tests/01_no_blockages_test.py b/compiler/router/tests/01_no_blockages_test.py new file mode 100644 index 00000000..7aa86779 --- /dev/null +++ b/compiler/router/tests/01_no_blockages_test.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python2.7 +"Run a regresion test the library cells for DRC" + +import unittest +from testutils import header +import sys,os +sys.path.append(os.path.join(sys.path[0],"../..")) +sys.path.append(os.path.join(sys.path[0],"..")) +import globals +import debug +import calibre +import vector + +class no_blockages_test(unittest.TestCase): + + def runTest(self): + globals.init_openram("config_{0}".format(OPTS.tech_name)) + + import router + #r=router.router("A_to_B_no_blockages.gds") + r=router.router("A_to_B_m1m2_blockages.gds") + + r.set_layers(("metal1","via1","metal2")) + + r.create_routing_grid() + + r.set_source("A") + + r.set_target("B") + + r.find_blockages() + r.rg.view() + + #drc_errors = calibre.run_drc(name, gds_name) + drc_errors = 1 + + # fails if there are any DRC errors on any cells + self.assertEqual(drc_errors, 0) + globals.end_openram() + + + + + + + + + +# instantiate a copy of the class to actually run the test +if __name__ == "__main__": + (OPTS, args) = globals.parse_args() + del sys.argv[1:] + header(__file__, OPTS.tech_name) + unittest.main() diff --git a/compiler/router/tests/A_to_B_m1_only.gds b/compiler/router/tests/A_to_B_m1_only.gds new file mode 100644 index 0000000000000000000000000000000000000000..71d3fa7639d092d9216ee8ec03549373936c3a86 GIT binary patch literal 2048 zcmeH@F-yZh7>1u*nrp$>RxGV)0_vcPf(oLzNGeXcI*CiG)xnC3{sR}mStu^u+?+c) zIXH+oioZY{{0V+%d(D)=RT8*+KHl7O2QM0hCUO*I#wbxCU|}?eEaNME)IM%rb}pde zdYg^w_1&lYuZ`{gQtxg*Ch`)uF1Oixj9b+DY^BvXKRj)ow8{CP$CbRJs|uNGj@j#G z_d_oZy4{|P_+=Nu9ms#61HA>g30Z}aT{QNhITgH0crak$1)@G!+FY3GJD=j;g5l&m z)YcN(T$t)l#{=Kc_^USwjX(UK>X)8j>J1F%TC%DMZ7xjp)q}jT2Zpon;9VuOx$xKj gOZ)C4%zT^n8^iYV!kpBeeOUup16c!E1OK6cA0F^LZ~y=R literal 0 HcmV?d00001 diff --git a/compiler/router/tests/A_to_B_m1m2_blockages.gds b/compiler/router/tests/A_to_B_m1m2_blockages.gds new file mode 100644 index 0000000000000000000000000000000000000000..8c445e1806afab0dd5bfd58d36ad01f493fbece1 GIT binary patch literal 2048 zcmeH^y-EW?6ot=DvXg+DAK8$&NgxRl8v`ncVk3#9(#FC{L`XDR7_o_OU?W%yQIK?Y z)+xop!a~GSd;+oX5j?YD98;`dzYBN1%h^3Mcg{jWK%!^~q%naMGVn0#_i4DOuy=&q z?;NyFyFDNiu~!@C%NvikpDWGb+~9hYS42DrRQH(oBA&3OstPOk_lgAh(4Xd@aVjaX?V!Wl3^@COo%N|027jjcr# z#KtCIk;cNtCWWQ{gZ>TCM%VX!Gn33l_mVr?UD$ccxAXR!?|pA(VGzNfQ;P5mqZmib z`z#vzJc%|E#F4qZd}nFx-XmbLT)i;8^XC1xYrE%XZccr@`e7QKYJ^q}w`t7dm8EX4 z`?TA8uy+3(P^kfCEaH5VL;y}V${ee_ zlp^mtP@8kO4J7sOoQd+8b1Esm2lSoCql-2B25{S`%b@>PF9AMc}I`ib*e%pv&N=H5de zZ`t}|PO&@ngzQu^Rg&?fjPa@J#rEdrJ3bpgv}y?5y2Tu-Q7=9JK$af0@q{@30+ltSG5T)y^<^HlrmPJ4&Kckgrf zR&!aWYEPc@-8l4dMsxW?c>k~Z2lV~{KG)8(V*u}G_(ORA0H1n!j~~tZ0pIEFJ${Jy z-Cru;)1|lQ(&rYVGc^7T&o75QPSpPKSZFp6{`=hC=~NePu+MVYy>nN+Ga!%ty+WQl z>yDmJc&f#~`D#D3|3mQDw`5RVs1ECQU$;_G$%>&+pirPtpirPtpitm{75D>m CV@`ko literal 0 HcmV?d00001 diff --git a/compiler/router/tests/A_to_B_m1m2_same_layer_pins.gds b/compiler/router/tests/A_to_B_m1m2_same_layer_pins.gds new file mode 100644 index 0000000000000000000000000000000000000000..05ec293639c4c15ddf684267946aee53322d6c75 GIT binary patch literal 4096 zcmeHIJ#Q015FKC6ixVyv$FYb5E+Ptw2r05;Bxn$%NCBdP3L&gW1S#S}C?Y_CB03sc zihzXDAkk2yp`f90L7D#m1tk?!aJ-q_vG**k$+^Ch?wr;4Eaq|VNq`;R$R2~@55B0M-y$rGs(Z6zf zpm+KDB&X;2>$n-e;zs-dD!*>yAEU7u&lvM`YkRZTr8pU6dV(`GixqyJe#a;(zYvbk zcC%g(I{BWW@>KMXDnH)8+6vxt%`k87cb^`fj+z^wU@dbU5Ea= z3Z2`nis>Ym!trXqrG69W;CpKOvdBF76vwV4@o;MMp;=y~1Em9{1Em9{1Em9S;2-P) BK_37B literal 0 HcmV?d00001 diff --git a/compiler/router/tests/A_to_B_no_blockages.gds b/compiler/router/tests/A_to_B_no_blockages.gds new file mode 100644 index 0000000000000000000000000000000000000000..46c65c506ffcbc1c171a0faf2dfc801664896b86 GIT binary patch literal 2048 zcmZQzV_;%nV~}BFVh~{9X5eQKVUT3dhO=dXEFLDdl+?7u(wq_o1`%dfJI79Q?_8D12aGlVo#bU|