diff --git a/gds/gpio_defaults_block_0402.gds b/gds/gpio_defaults_block_0402.gds deleted file mode 100644 index d026e809..00000000 Binary files a/gds/gpio_defaults_block_0402.gds and /dev/null differ diff --git a/gds/gpio_defaults_block_1403.gds b/gds/gpio_defaults_block_1403.gds deleted file mode 100644 index 53cb8a9c..00000000 Binary files a/gds/gpio_defaults_block_1403.gds and /dev/null differ diff --git a/gds/gpio_defaults_block_1803.gds b/gds/gpio_defaults_block_1803.gds deleted file mode 100644 index 7ba12701..00000000 Binary files a/gds/gpio_defaults_block_1803.gds and /dev/null differ diff --git a/scripts/check_density.py b/scripts/check_density.py new file mode 100755 index 00000000..25f44bab --- /dev/null +++ b/scripts/check_density.py @@ -0,0 +1,616 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2020 Efabless Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# SPDX-License-Identifier: Apache-2.0 + +# +# check_density.py --- +# +# Run density checks on the final (filled) GDS. +# + +import sys +import os +import re +import select +import subprocess + +def usage(): + print("Usage:") + print("check_density.py [] [-keep]") + print("") + print("where:") + print(" is the path to the project top level directory.") + print("") + print(" If is not given, then it is assumed to be the cwd.") + print(" If '-keep' is specified, then keep the check script.") + return 0 + + +if __name__ == '__main__': + + optionlist = [] + arguments = [] + + debugmode = False + keepmode = False + + for option in sys.argv[1:]: + if option.find('-', 0) == 0: + optionlist.append(option) + else: + arguments.append(option) + + if len(arguments) > 1: + print("Wrong number of arguments given to check_density.py.") + usage() + sys.exit(0) + + if len(arguments) == 1: + user_project_path = arguments[0] + else: + user_project_path = os.getcwd() + + # Check for valid user path + + if not os.path.isdir(user_project_path): + print('Error: Project path "' + user_project_path + '" does not exist or is not readable.') + sys.exit(1) + + # Check for valid user ID + user_id_value = None + if os.path.isfile(user_project_path + '/info.yaml'): + with open(user_project_path + '/info.yaml', 'r') as ifile: + infolines = ifile.read().splitlines() + for line in infolines: + kvpair = line.split(':') + if len(kvpair) == 2: + key = kvpair[0].strip() + value = kvpair[1].strip() + if key == 'project_id': + user_id_value = value.strip('"\'') + break + + if user_id_value: + project = 'caravel' + project_with_id = 'caravel_' + user_id_value + else: + print('Error: No project_id found in info.yaml file.') + sys.exit(1) + + if '-debug' in optionlist: + debugmode = True + if '-keep' in optionlist: + keepmode = True + + magpath = user_project_path + '/mag' + rcfile = magpath + '/.magicrc' + + with open(magpath + '/check_density.tcl', 'w') as ofile: + print('#!/bin/env wish', file=ofile) + print('crashbackups stop', file=ofile) + print('drc off', file=ofile) + print('snap internal', file=ofile) + + print('set starttime [orig_clock format [orig_clock seconds] -format "%D %T"]', file=ofile) + print('puts stdout "Started reading GDS: $starttime"', file=ofile) + print('', file=ofile) + print('flush stdout', file=ofile) + print('update idletasks', file=ofile) + + # Read final project from .gds + print('gds readonly true', file=ofile) + print('gds rescale false', file=ofile) + print('gds read ../gds/' + project_with_id + '.gds', file=ofile) + print('', file=ofile) + + print('set midtime [orig_clock format [orig_clock seconds] -format "%D %T"]', file=ofile) + print('puts stdout "Starting density checks: $midtime"', file=ofile) + print('', file=ofile) + print('flush stdout', file=ofile) + print('update idletasks', file=ofile) + + # Get step box dimensions (700um for size and 70um for FOM step) + # Use 350um for stepping on other layers. + print('box values 0 0 0 0', file=ofile) + # print('box size 700um 700um', file=ofile) + # print('set stepbox [box values]', file=ofile) + # print('set stepwidth [lindex $stepbox 2]', file=ofile) + # print('set stepheight [lindex $stepbox 3]', file=ofile) + + print('box size 70um 70um', file=ofile) + print('set stepbox [box values]', file=ofile) + print('set stepsizex [lindex $stepbox 2]', file=ofile) + print('set stepsizey [lindex $stepbox 3]', file=ofile) + + print('select top cell', file=ofile) + print('expand', file=ofile) + + # Modify the box to be inside the seal ring area (shrink 5um) + print('box grow c -5um', file=ofile) + print('set fullbox [box values]', file=ofile) + + print('set xmax [lindex $fullbox 2]', file=ofile) + print('set xmin [lindex $fullbox 0]', file=ofile) + print('set fullwidth [expr {$xmax - $xmin}]', file=ofile) + print('set xtiles [expr {int(ceil(($fullwidth + 0.0) / $stepsizex))}]', file=ofile) + print('set ymax [lindex $fullbox 3]', file=ofile) + print('set ymin [lindex $fullbox 1]', file=ofile) + print('set fullheight [expr {$ymax - $ymin}]', file=ofile) + print('set ytiles [expr {int(ceil(($fullheight + 0.0) / $stepsizey))}]', file=ofile) + print('box size $stepsizex $stepsizey', file=ofile) + print('set xbase [lindex $fullbox 0]', file=ofile) + print('set ybase [lindex $fullbox 1]', file=ofile) + print('', file=ofile) + + print('puts stdout "XTILES: $xtiles"', file=ofile) + print('puts stdout "YTILES: $ytiles"', file=ofile) + print('', file=ofile) + + # Need to know what fraction of a full tile is the last row and column + print('set xfrac [expr {($xtiles * $stepsizex - $fullwidth + 0.0) / $stepsizex}]', file=ofile) + print('set yfrac [expr {($ytiles * $stepsizey - $fullheight + 0.0) / $stepsizey}]', file=ofile) + print('puts stdout "XFRAC: $xfrac"', file=ofile) + print('puts stdout "YFRAC: $yfrac"', file=ofile) + + print('cif ostyle density', file=ofile) + + # Process density at steps. For efficiency, this is done in 70x70 um + # areas, dumped to a file, and then aggregated into the 700x700 areas. + + print('for {set y 0} {$y < $ytiles} {incr y} {', file=ofile) + print(' for {set x 0} {$x < $xtiles} {incr x} {', file=ofile) + print(' set xlo [expr $xbase + $x * $stepsizex]', file=ofile) + print(' set ylo [expr $ybase + $y * $stepsizey]', file=ofile) + print(' set xhi [expr $xlo + $stepsizex]', file=ofile) + print(' set yhi [expr $ylo + $stepsizey]', file=ofile) + print(' box values $xlo $ylo $xhi $yhi', file=ofile) + + # Flatten this area + print(' flatten -dobbox -nolabels tile', file=ofile) + print(' load tile', file=ofile) + print(' select top cell', file=ofile) + + # Run density check for each layer + print(' puts stdout "Density results for tile x=$x y=$y"', file=ofile) + + print(' set fdens [cif list cover fom_all]', file=ofile) + print(' set pdens [cif list cover poly_all]', file=ofile) + print(' set ldens [cif list cover li_all]', file=ofile) + print(' set m1dens [cif list cover m1_all]', file=ofile) + print(' set m2dens [cif list cover m2_all]', file=ofile) + print(' set m3dens [cif list cover m3_all]', file=ofile) + print(' set m4dens [cif list cover m4_all]', file=ofile) + print(' set m5dens [cif list cover m5_all]', file=ofile) + print(' puts stdout "FOM: $fdens"', file=ofile) + print(' puts stdout "POLY: $pdens"', file=ofile) + print(' puts stdout "LI1: $ldens"', file=ofile) + print(' puts stdout "MET1: $m1dens"', file=ofile) + print(' puts stdout "MET2: $m2dens"', file=ofile) + print(' puts stdout "MET3: $m3dens"', file=ofile) + print(' puts stdout "MET4: $m4dens"', file=ofile) + print(' puts stdout "MET5: $m5dens"', file=ofile) + print(' flush stdout', file=ofile) + print(' update idletasks', file=ofile) + + print(' load ' + project_with_id, file=ofile) + print(' cellname delete tile', file=ofile) + + print(' }', file=ofile) + print('}', file=ofile) + + print('set endtime [orig_clock format [orig_clock seconds] -format "%D %T"]', file=ofile) + print('puts stdout "Ended: $endtime"', file=ofile) + print('', file=ofile) + + + myenv = os.environ.copy() + # Real views are necessary for the DRC checks + myenv['MAGTYPE'] = 'mag' + + print('Running density checks on file ' + project_with_id + '.gds', flush=True) + + mproc = subprocess.Popen(['magic', '-dnull', '-noconsole', + '-rcfile', rcfile, magpath + '/check_density.tcl'], + stdin = subprocess.DEVNULL, + stdout = subprocess.PIPE, + stderr = subprocess.PIPE, + cwd = magpath, + env = myenv, + universal_newlines = True) + + # Use signal to poll the process and generate any output as it arrives + + dlines = [] + + while mproc: + status = mproc.poll() + if status != None: + try: + output = mproc.communicate(timeout=1) + except ValueError: + print('Magic forced stop, status ' + str(status)) + sys.exit(1) + else: + outlines = output[0] + errlines = output[1] + for line in outlines.splitlines(): + dlines.append(line) + print(line) + for line in errlines.splitlines(): + print(line) + print('Magic exited with status ' + str(status)) + if int(status) != 0: + sys.exit(int(status)) + else: + break + else: + n = 0 + while True: + n += 1 + if n > 100: + n = 0 + status = mproc.poll() + if status != None: + break + sresult = select.select([mproc.stdout, mproc.stderr], [], [], 0.5)[0] + if mproc.stdout in sresult: + outstring = mproc.stdout.readline().strip() + dlines.append(outstring) + print(outstring) + elif mproc.stderr in sresult: + outstring = mproc.stderr.readline().strip() + print(outstring) + else: + break + + fomfill = [] + polyfill = [] + lifill = [] + met1fill = [] + met2fill = [] + met3fill = [] + met4fill = [] + met5fill = [] + xtiles = 0 + ytiles = 0 + xfrac = 0.0 + yfrac = 0.0 + + for line in dlines: + dpair = line.split(':') + if len(dpair) == 2: + layer = dpair[0] + try: + density = float(dpair[1].strip()) + except: + continue + if layer == 'FOM': + fomfill.append(density) + elif layer == 'POLY': + polyfill.append(density) + elif layer == 'LI1': + lifill.append(density) + elif layer == 'MET1': + met1fill.append(density) + elif layer == 'MET2': + met2fill.append(density) + elif layer == 'MET3': + met3fill.append(density) + elif layer == 'MET4': + met4fill.append(density) + elif layer == 'MET5': + met5fill.append(density) + elif layer == 'XTILES': + xtiles = int(dpair[1].strip()) + elif layer == 'YTILES': + ytiles = int(dpair[1].strip()) + elif layer == 'XFRAC': + xfrac = float(dpair[1].strip()) + elif layer == 'YFRAC': + yfrac = float(dpair[1].strip()) + + if ytiles == 0 or xtiles == 0: + print('Failed to read XTILES or YTILES from output.') + sys.exit(1) + + total_tiles = (ytiles - 9) * (xtiles - 9) + + print('') + print('Density results (total tiles = ' + str(total_tiles) + '):') + + # For FOM, step at 70um intervals (same as 70um check area) + fomstep = 1 + + # For poly, step only at 700um intervals (10 * 70um check area) + polystep = 10 + + # For all metals, step only at 350um intervals (5 * 70um check area) + metalstep = 5 + + # Full areas are 10 x 10 tiles = 100. But the right and top sides are + # not full tiles, so the full area must be prorated. + + sideadjust = 90.0 + (10.0 * xfrac) + topadjust = 90.0 + (10.0 * yfrac) + corneradjust = 81.0 + (9.0 * xfrac) + (9.0 * yfrac) + (xfrac * yfrac) + + print('') + print('FOM Density:') + for y in range(0, ytiles - 9, fomstep): + if y == ytiles - 10: + atotal = topadjust + else: + atotal = 100.0 + for x in range(0, xtiles - 9, fomstep): + if x == xtiles - 10: + if y == ytiles - 10: + atotal = corneradjust + else: + atotal = sideadjust + fomaccum = 0 + for w in range(y, y + 10): + base = xtiles * w + x + fomaccum += sum(fomfill[base : base + 10]) + + fomaccum /= atotal + print('Tile (' + str(x) + ', ' + str(y) + '): ' + str(fomaccum)) + if fomaccum < 0.33: + print('***Error: FOM Density < 33%') + elif fomaccum > 0.57: + print('***Error: FOM Density > 57%') + + print('') + print('POLY Density:') + for y in range(0, ytiles - 9, polystep): + if y == ytiles - 10: + atotal = topadjust + else: + atotal = 100.0 + for x in range(0, xtiles - 9, polystep): + if x == xtiles - 10: + if y == ytiles - 10: + atotal = corneradjust + else: + atotal = sideadjust + polyaccum = 0 + for w in range(y, y + 10): + base = xtiles * w + x + polyaccum += sum(polyfill[base : base + 10]) + + polyaccum /= atotal + print('Tile (' + str(x) + ', ' + str(y) + '): ' + str(polyaccum)) + + print('') + print('LI Density:') + for y in range(0, ytiles - 9, metalstep): + if y == ytiles - 10: + atotal = topadjust + else: + atotal = 100.0 + for x in range(0, xtiles - 9, metalstep): + if x == xtiles - 10: + if y == ytiles - 10: + atotal = corneradjust + else: + atotal = sideadjust + liaccum = 0 + for w in range(y, y + 10): + base = xtiles * w + x + liaccum += sum(lifill[base : base + 10]) + + liaccum /= atotal + print('Tile (' + str(x) + ', ' + str(y) + '): ' + str(liaccum)) + if liaccum < 0.35: + print('***Error: LI Density < 35%') + elif liaccum > 0.60: + print('***Error: LI Density > 60%') + + print('') + print('MET1 Density:') + for y in range(0, ytiles - 9, metalstep): + if y == ytiles - 10: + atotal = topadjust + else: + atotal = 100.0 + for x in range(0, xtiles - 9, metalstep): + if x == xtiles - 10: + if y == ytiles - 10: + atotal = corneradjust + else: + atotal = sideadjust + met1accum = 0 + for w in range(y, y + 10): + base = xtiles * w + x + met1accum += sum(met1fill[base : base + 10]) + + met1accum /= atotal + print('Tile (' + str(x) + ', ' + str(y) + '): ' + str(met1accum)) + if met1accum < 0.35: + print('***Error: MET1 Density < 35%') + elif met1accum > 0.60: + print('***Error: MET1 Density > 60%') + + print('') + print('MET2 Density:') + for y in range(0, ytiles - 9, metalstep): + if y == ytiles - 10: + atotal = topadjust + else: + atotal = 100.0 + for x in range(0, xtiles - 9, metalstep): + if x == xtiles - 10: + if y == ytiles - 10: + atotal = corneradjust + else: + atotal = sideadjust + met2accum = 0 + for w in range(y, y + 10): + base = xtiles * w + x + met2accum += sum(met2fill[base : base + 10]) + + met2accum /= atotal + print('Tile (' + str(x) + ', ' + str(y) + '): ' + str(met2accum)) + if met2accum < 0.35: + print('***Error: MET2 Density < 35%') + elif met2accum > 0.60: + print('***Error: MET2 Density > 60%') + + print('') + print('MET3 Density:') + for y in range(0, ytiles - 9, metalstep): + if y == ytiles - 10: + atotal = topadjust + else: + atotal = 100.0 + for x in range(0, xtiles - 9, metalstep): + if x == xtiles - 10: + if y == ytiles - 10: + atotal = corneradjust + else: + atotal = sideadjust + met3accum = 0 + for w in range(y, y + 10): + base = xtiles * w + x + met3accum += sum(met3fill[base : base + 10]) + + met3accum /= atotal + print('Tile (' + str(x) + ', ' + str(y) + '): ' + str(met3accum)) + if met3accum < 0.35: + print('***Error: MET3 Density < 35%') + elif met3accum > 0.60: + print('***Error: MET3 Density > 60%') + + print('') + print('MET4 Density:') + for y in range(0, ytiles - 9, metalstep): + if y == ytiles - 10: + atotal = topadjust + else: + atotal = 100.0 + for x in range(0, xtiles - 9, metalstep): + if x == xtiles - 10: + if y == ytiles - 10: + atotal = corneradjust + else: + atotal = sideadjust + met4accum = 0 + for w in range(y, y + 10): + base = xtiles * w + x + met4accum += sum(met4fill[base : base + 10]) + + met4accum /= atotal + print('Tile (' + str(x) + ', ' + str(y) + '): ' + str(met4accum)) + if met4accum < 0.35: + print('***Error: MET4 Density < 35%') + elif met4accum > 0.60: + print('***Error: MET4 Density > 60%') + + print('') + print('MET5 Density:') + for y in range(0, ytiles - 9, metalstep): + if y == ytiles - 10: + atotal = topadjust + else: + atotal = 100.0 + for x in range(0, xtiles - 9, metalstep): + if x == xtiles - 10: + if y == ytiles - 10: + atotal = corneradjust + else: + atotal = sideadjust + met5accum = 0 + for w in range(y, y + 10): + base = xtiles * w + x + met5accum += sum(met5fill[base : base + 10]) + + met5accum /= atotal + print('Tile (' + str(x) + ', ' + str(y) + '): ' + str(met5accum)) + if met5accum < 0.45: + print('***Error: MET5 Density < 45%') + elif met5accum > 0.76: + print('***Error: MET5 Density > 76%') + + print('') + print('Whole-chip density results:') + + atotal = ((xtiles - 1.0) * (ytiles - 1.0)) + ((ytiles - 1.0) * xfrac) + ((xtiles - 1.0) * yfrac) + (xfrac * yfrac) + + fomaccum = sum(fomfill) / atotal + print('') + print('FOM Density: ' + str(fomaccum)) + if fomaccum < 0.33: + print('***Error: FOM Density < 33%') + elif fomaccum > 0.57: + print('***Error: FOM Density > 57%') + + polyaccum = sum(polyfill) / atotal + print('') + print('POLY Density: ' + str(polyaccum)) + + liaccum = sum(lifill) / atotal + print('') + print('LI Density: ' + str(liaccum)) + if liaccum < 0.35: + print('***Error: LI Density < 35%') + elif liaccum > 0.60: + print('***Error: LI Density > 60%') + + met1accum = sum(met1fill) / atotal + print('') + print('MET1 Density: ' + str(met1accum)) + if met1accum < 0.35: + print('***Error: MET1 Density < 35%') + elif met1accum > 0.60: + print('***Error: MET1 Density > 60%') + + met2accum = sum(met2fill) / atotal + print('') + print('MET2 Density: ' + str(met2accum)) + if met2accum < 0.35: + print('***Error: MET2 Density < 35%') + elif met2accum > 0.60: + print('***Error: MET2 Density > 60%') + + met3accum = sum(met3fill) / atotal + print('') + print('MET3 Density: ' + str(met3accum)) + if met3accum < 0.35: + print('***Error: MET3 Density < 35%') + elif met3accum > 0.60: + print('***Error: MET3 Density > 60%') + + met4accum = sum(met4fill) / atotal + print('') + print('MET4 Density: ' + str(met4accum)) + if met4accum < 0.35: + print('***Error: MET4 Density < 35%') + elif met4accum > 0.60: + print('***Error: MET4 Density > 60%') + + met5accum = sum(met5fill) / atotal + print('') + print('MET5 Density: ' + str(met5accum)) + if met5accum < 0.45: + print('***Error: MET5 Density < 45%') + elif met5accum > 0.76: + print('***Error: MET5 Density > 76%') + + if not keepmode: + if os.path.isfile(magpath + '/check_density.tcl'): + os.remove(magpath + '/check_density.tcl') + + print('') + print('Done!') + sys.exit(0) diff --git a/scripts/compositor.py b/scripts/compositor.py new file mode 100755 index 00000000..efa0b4ee --- /dev/null +++ b/scripts/compositor.py @@ -0,0 +1,253 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2020 Efabless Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# SPDX-License-Identifier: Apache-2.0 + +# +# compositor.py --- +# +# Compose the final GDS for caravel from the caravel GDS, seal ring +# GDS, and fill GDS. +# + +import sys +import os +import re +import subprocess + +def usage(): + print("Usage:") + print("compositor.py is a character string of eight hex digits, and") + print(" is the path to the project top level directory.") + print(" is the path to the mag directory.") + print(" is the path to the gds directory.") + print("") + print(" If is not given, then it must exist in the info.yaml file.") + print(" If is not given, then it is assumed to be the cwd.") + print(" If is not given, then it is assumed to be the /tmp.") + print(" If is not given, then it is assumed to be the /gds.") + print(" If '-keep' is specified, then keep the generation script.") + return 0 + +if __name__ == '__main__': + + optionlist = [] + arguments = [] + + debugmode = False + keepmode = False + + for option in sys.argv[1:]: + if option.find('-', 0) == 0: + optionlist.append(option) + else: + arguments.append(option) + + if len(arguments) != 5: + print("Wrong number of arguments given to compositor.py.") + usage() + sys.exit(0) + + user_id_value = arguments[0] + project = arguments[1] + user_project_path = arguments[2] + mag_dir_path = arguments[3] + gds_dir_path = arguments[4] + + # if len(arguments) > 0: + # user_id_value = arguments[0] + + # Convert to binary + try: + user_id_int = int('0x' + user_id_value, 0) + user_id_bits = '{0:032b}'.format(user_id_int) + except: + print("User ID not recognized") + usage() + sys.exit(1) + + # if len(arguments) == 2 and user_project_path == None: + # user_project_path = arguments[1] + # mag_dir_path = user_project_path + "/mag" + # gds_dir_path = "../gds" + # if len(arguments) == 3 and user_project_path == None: + # user_project_path = arguments[1] + # mag_dir_path = arguments[2] + # gds_dir_path = "../gds" + # if len(arguments) == 4: + # user_project_path = arguments[1] + # mag_dir_path = arguments[2] + # gds_dir_path = arguments[3] + # elif len(arguments) == 3 and user_project_path != None: + # mag_dir_path = arguments[1] + # gds_dir_path = arguments[2] + # else: + # user_project_path = os.getcwd() + # mag_dir_path = user_project_path + "/mag" + # gds_dir_path = "../gds" + + # Check for valid user path + + if not os.path.isdir(user_project_path): + print('Error: Project path "' + user_project_path + '" does not exist or is not readable.') + sys.exit(1) + + # Check for valid mag path + + if not os.path.isdir(mag_dir_path): + print('Error: Mag directory path "' + mag_dir_path + '" does not exist or is not readable.') + sys.exit(1) + + # Check for valid gds path + + if not os.path.isdir(gds_dir_path): + print('Error: GDS directory path "' + gds_dir_path + '" does not exist or is not readable.') + sys.exit(1) + + # Check for valid user ID + # if not user_id_value: + # if os.path.isfile(user_project_path + '/info.yaml'): + # with open(user_project_path + '/info.yaml', 'r') as ifile: + # infolines = ifile.read().splitlines() + # for line in infolines: + # kvpair = line.split(':') + # if len(kvpair) == 2: + # key = kvpair[0].strip() + # value = kvpair[1].strip() + # if key == 'project_id': + # user_id_value = value.strip('"\'') + # break + + if user_id_value: + # project = 'caravel' + # project_with_id = project + '_' + user_id_value + project_with_id = 'caravel_' + user_id_value + user_id_decimal = str(int(user_id_value, 16)) + else: + print('Error: No project_id found in info.yaml file.') + sys.exit(1) + + if '-debug' in optionlist: + debugmode = True + if '-keep' in optionlist: + keepmode = True + + magpath = mag_dir_path + rcfile = magpath + '/.magicrc' + + gdspath = gds_dir_path + + # The compositor script will create .mag, but is uses + # "load", so the file must not already exist. + + if os.path.isfile(user_project_path + '/mag/' + project_with_id + '.mag'): + print('Error: File ' + project_with_id + '.mag exists already! Exiting. . .') + sys.exit(1) + + with open(user_project_path + '/mag/compose_final.tcl', 'w') as ofile: + print('#!/bin/env wish', file=ofile) + print('drc off', file=ofile) + # Set the random seed from the project ID + print('random seed ' + user_id_decimal, file=ofile) + + # Read project from .mag but set GDS properties so that it points + # to the GDS file created by "make ship". + print('load ' + project + ' -dereference', file=ofile) + print('property GDS_FILE ' + gdspath + '/' + project + '.gds', file=ofile) + print('property GDS_START 0', file=ofile) + print('select top cell', file=ofile) + print('set bbox [box values]', file=ofile) + + # Ceate a cell to represent the generated fill. There are + # no magic layers corresponding to the fill shape data, and + # it's gigabytes anyway, so we don't want to deal with any + # actual data. So it's just a placeholder. + + print('load ' + project_with_id + '_fill_pattern -quiet', file=ofile) + print('snap internal', file=ofile) + print('box values {*}$bbox', file=ofile) + print('paint comment', file=ofile) + print('property GDS_FILE ' + gdspath + '/' + project_with_id + '_fill_pattern.gds', file=ofile) + print('property GDS_START 0', file=ofile) + print('property FIXED_BBOX "$bbox"', file=ofile) + + # Create a new project top level and place the fill cell. + print('load ' + project_with_id + ' -quiet', file=ofile) + print('box values 0 0 0 0', file=ofile) + print('box position 6um 6um', file=ofile) + print('getcell ' + project + ' child 0 0', file=ofile) + print('getcell ' + project_with_id + '_fill_pattern child 0 0', file=ofile) + + # Move existing origin to (6um, 6um) for seal ring placement + # print('move origin -6um -6um', file=ofile) + + # Read in abstract view of seal ring + print('box position 0 0', file=ofile) + print('getcell advSeal_6um_gen', file=ofile) + + # Write out completed project as "caravel_" + the user ID + # print('save ' + user_project_path + '/mag/' + project_with_id, file=ofile) + + # Generate final GDS + print('puts stdout "Writing final GDS. . . "', file=ofile) + print('flush stdout', file=ofile) + print('gds undefined allow', file=ofile) + print('cif *hier write disable', file=ofile) + print('gds write ' + gdspath + '/' + project_with_id + '.gds', file=ofile) + print('quit -noprompt', file=ofile) + + myenv = os.environ.copy() + # Abstract views are appropriate for final composition + myenv['MAGTYPE'] = 'maglef' + + print('Building final GDS file ' + project_with_id + '.gds', flush=True) + + mproc = subprocess.run(['magic', '-dnull', '-noconsole', + '-rcfile', rcfile, user_project_path + '/mag/compose_final.tcl'], + stdin = subprocess.DEVNULL, + stdout = subprocess.PIPE, + stderr = subprocess.PIPE, + cwd = magpath, + env = myenv, + universal_newlines = True) + if mproc.stdout: + for line in mproc.stdout.splitlines(): + print(line) + if mproc.stderr: + # NOTE: Until there is a "load -quiet" option in magic, loading + # a new cell generates an error. This code ignores the error. + newlines = [] + for line in mproc.stderr.splitlines(): + if line.endswith("_fill_pattern.mag couldn't be read"): + continue + if line.startswith("No such file or directory"): + continue + else: + newlines.append(line) + + if len(newlines) > 0: + print('Error message output from magic:') + for line in newlines: + print(line) + if mproc.returncode != 0: + print('ERROR: Magic exited with status ' + str(mproc.returncode)) + + if not keepmode: + os.remove(user_project_path + '/mag/compose_final.tcl') + + print('Done!') + exit(0) diff --git a/scripts/count_lvs.py b/scripts/count_lvs.py new file mode 100755 index 00000000..2362e011 --- /dev/null +++ b/scripts/count_lvs.py @@ -0,0 +1,121 @@ +#!ENV_PATH python3 +# +#--------------------------------------------------------- +# LVS failure check +# +# This is a Python script that parses the comp.json +# output from netgen and reports on the number of +# errors in the top-level netlist. +# +#--------------------------------------------------------- +# Written by Tim Edwards +# efabless, inc. +# Pulled from qflow GUI as standalone script Aug 20, 2018 +#--------------------------------------------------------- + +import os +import re +import sys +import json +import argparse + +def count_LVS_failures(filename): + with open(filename, 'r') as cfile: + lvsdata = json.load(cfile) + + # Count errors in the JSON file + failures = 0 + devfail = 0 + netfail = 0 + pinfail = 0 + propfail = 0 + netdiff = 0 + devdiff = 0 + ncells = len(lvsdata) + for c in range(0, ncells): + cellrec = lvsdata[c] + + if c == ncells - 1: + topcell = True + else: + topcell = False + + # Most errors must only be counted for the top cell, because individual + # failing cells are flattened and the matching attempted again on the + # flattened netlist. + + if topcell: + if 'devices' in cellrec: + devices = cellrec['devices'] + devlist = [val for pair in zip(devices[0], devices[1]) for val in pair] + devpair = list(devlist[p:p + 2] for p in range(0, len(devlist), 2)) + for dev in devpair: + c1dev = dev[0] + c2dev = dev[1] + diffdevs = abs(c1dev[1] - c2dev[1]) + failures += diffdevs + devdiff += diffdevs + + if 'nets' in cellrec: + nets = cellrec['nets'] + diffnets = abs(nets[0] - nets[1]) + failures += diffnets + netdiff += diffnets + + if 'badnets' in cellrec: + badnets = cellrec['badnets'] + failures += len(badnets) + netfail += len(badnets) + + if 'badelements' in cellrec: + badelements = cellrec['badelements'] + failures += len(badelements) + devfail += len(badelements) + + if 'pins' in cellrec: + pins = cellrec['pins'] + pinlist = [val for pair in zip(pins[0], pins[1]) for val in pair] + pinpair = list(pinlist[p:p + 2] for p in range(0, len(pinlist), 2)) + for pin in pinpair: + # Avoid flagging global vs. local names, e.g., "gnd" vs. "gnd!," + # and ignore case when comparing pins. + pin0 = re.sub('!$', '', pin[0].lower()) + pin1 = re.sub('!$', '', pin[1].lower()) + if pin0 != pin1: + # The text "(no pin)" indicates a missing pin that can be + # ignored because the pin in the other netlist is a no-connect + if pin0 != '(no pin)' and pin1 != '(no pin)': + failures += 1 + pinfail += 1 + + # Property errors must be counted for every cell + if 'properties' in cellrec: + properties = cellrec['properties'] + failures += len(properties) + propfail += len(properties) + + return [failures, netfail, devfail, pinfail, propfail, netdiff, devdiff] + +if __name__ == '__main__': + + parser = argparse.ArgumentParser(description='Parses netgen lvs') + parser.add_argument('--file', '-f', required=True) + args = parser.parse_args() + failures = count_LVS_failures(args.file) + + total = failures[0] + if total > 0: + failed = True + print('LVS reports:') + print(' net count difference = ' + str(failures[5])) + print(' device count difference = ' + str(failures[6])) + print(' unmatched nets = ' + str(failures[1])) + print(' unmatched devices = ' + str(failures[2])) + print(' unmatched pins = ' + str(failures[3])) + print(' property failures = ' + str(failures[4])) + else: + print('LVS reports no net, device, pin, or property mismatches.') + + print('') + print('Total errors = ' + str(total)) + \ No newline at end of file diff --git a/scripts/create-caravel-diagram.py b/scripts/create-caravel-diagram.py new file mode 100755 index 00000000..bfb4e3cc --- /dev/null +++ b/scripts/create-caravel-diagram.py @@ -0,0 +1,126 @@ +# SPDX-FileCopyrightText: 2020 Efabless Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# SPDX-License-Identifier: Apache-2.0 +import sys +import os +import subprocess +from pathlib import Path +import argparse +from tempfile import mkstemp +import re + + +def remove_inouts(jsonpath, replacewith='input'): + """Replaces inouts with either input or output statements. + + Netlistsvg does not parse inout ports as for now, so they need to be + replaced with either input or output to produce a diagram. + + Parameters + ---------- + jsonpath : str + Path to JSON file to fix + replacewith : str + The string to replace 'inout', can be 'input' or 'output' + """ + assert replacewith in ['input', 'output'] + with open(jsonpath, 'r') as withinouts: + lines = withinouts.readlines() + with open(jsonpath, 'w') as withoutinouts: + for line in lines: + withoutinouts.write(re.sub('inout', replacewith, line)) + + +def main(argv): + parser = argparse.ArgumentParser(argv[0]) + parser.add_argument( + 'verilog_rtl_dir', + help="Path to the project's verilog/rtl directory", + type=Path) + parser.add_argument( + 'output', + help="Path to the output SVG file", + type=Path) + parser.add_argument( + '--num-iopads', + help='Number of iopads to render', + type=int, + default=38) + parser.add_argument( + '--yosys-executable', + help='Path to yosys executable', + type=Path, + default='yosys') + parser.add_argument( + '--netlistsvg-executable', + help='Path to netlistsvg executable', + type=Path, + default='netlistsvg') + parser.add_argument( + '--inouts-as', + help='To what kind of IO should inout ports be replaced', + choices=['input', 'output'], + default='input' + ) + + args = parser.parse_args(argv[1:]) + + fd, jsonpath = mkstemp(suffix='-yosys.json') + os.close(fd) + + yosyscommand = [ + f'{str(args.yosys_executable)}', + '-p', + 'read_verilog pads.v defines.v; ' + + 'read_verilog -lib -overwrite *.v; ' + + f'verilog_defines -DMPRJ_IO_PADS={args.num_iopads}; ' + + 'read_verilog -overwrite caravel.v; ' + + 'hierarchy -top caravel; ' + + 'proc; ' + + 'opt; ' + + f'write_json {jsonpath}; ' + ] + + result = subprocess.run( + yosyscommand, + cwd=args.verilog_rtl_dir, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT + ) + + exitcode = 0 + if result.returncode != 0: + print(f'Failed to run: {" ".join(yosyscommand)}', file=sys.stderr) + print(result.stdout.decode()) + exitcode = result.returncode + else: + # TODO once netlistsvg supports inout ports, this should be removed + remove_inouts(jsonpath, args.inouts_as) + command = f'{args.netlistsvg_executable} {jsonpath} -o {args.output}' + result = subprocess.run( + command.split(), + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT + ) + if result.returncode != 0: + print(f'Failed to run: {command}', file=sys.stderr) + print(result.stdout.decode()) + exitcode = result.returncode + + os.unlink(jsonpath) + sys.exit(exitcode) + + +if __name__ == '__main__': + sys.exit(main(sys.argv)) diff --git a/scripts/gen_gpio_defaults_orig.py b/scripts/gen_gpio_defaults_orig.py deleted file mode 100755 index 560d82f0..00000000 --- a/scripts/gen_gpio_defaults_orig.py +++ /dev/null @@ -1,318 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-FileCopyrightText: 2020 Efabless Corporation -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# SPDX-License-Identifier: Apache-2.0 - -#---------------------------------------------------------------------- -# -# gen_gpio_defaults.py --- -# -# Manipulate the magic database and GDS to create and apply defaults -# to the GPIO control blocks based on the user's specification in the -# user_defines.v file. -# -# The GPIO defaults block contains 13 bits that set the state of the -# GPIO on power-up. GPIOs 0 to 4 in the user project area are fixed -# and cannot be modified (to maintain access to the housekeeping SPI -# on startup). GPIOs 5 to 37 are by default set to be an input pad -# controlled by the user project. The file "user_defines.v" contains -# the state specified by the user for each GPIO pad, and is what is -# used in verilog simulation. -# -# This script parses the user_defines.v file to determine the state -# of each GPIO. Then it creates as many new layouts as needed to -# represent all unique states, modifies the caravel.mag layout -# to replace the default layouts with the new ones as needed, and -# generates GDS files for each of the layouts. -# -# gpio_defaults_block layout map: -# Positions marked (in microns) for value = 0. For value = 1, move -# the via 0.69um to the left. The given position is the lower left -# corner position of the via. The via itself is 0.17um x 0.17um. -# The values below are for the file gpio_defaults_block_1403. -# Positions marked "Y" for "Programmed One?" are already moved to -# the left, and so should be move 0.69um to the right if the bit -# should be zero. -# -# Signal Via position (um) -# name X Y -#------------------------------------------------------------------- -# gpio_defaults[0] 5.435 4.165 -# gpio_defaults[1] 6.815 3.825 -# gpio_defaults[2] 8.195 4.165 -# gpio_defaults[3] 9.575 3.825 -# gpio_defaults[4] 10.955 3.825 -# gpio_defaults[5] 12.565 3.825 -# gpio_defaults[6] 14.865 3.825 -# gpio_defaults[7] 17.165 3.825 -# gpio_defaults[8] 19.465 3.825 -# gpio_defaults[9] 21.765 3.825 -# gpio_defaults[10] 24.755 3.825 -# gpio_defaults[11] 27.055 3.825 -# gpio_defaults[12] 23.605 4.165 -#------------------------------------------------------------------- - -import os -import sys -import re - -def usage(): - print('Usage:') - print('gen_gpio_defaults.py []') - print('') - print('where:') - print(' is the path to the project top level directory.') - print('') - print(' If is not given, then it is assumed to be the cwd.') - print(' The file "user_defines.v" must exist in verilog/rtl/ relative to') - print(' .') - return 0 - -if __name__ == '__main__': - - # Coordinate pairs in microns for the zero position on each bit - via_pos = [[5.435, 4.165], [6.815, 3.825], [8.195, 4.165], [9.575, 3.825], - [10.955, 3.825], [12.565, 3.825], [14.865, 3.825], [17.165, 3.825], - [19.465, 3.825], [21.765, 3.825], [24.755, 3.825], [27.055, 3.825], - [23.605, 4.165]] - - optionlist = [] - arguments = [] - - debugmode = False - testmode = False - - for option in sys.argv[1:]: - if option.find('-', 0) == 0: - optionlist.append(option) - else: - arguments.append(option) - - if len(arguments) > 2: - print("Wrong number of arguments given to gen_gpio_defaults.py.") - usage() - sys.exit(0) - - if '-debug' in optionlist: - debugmode = True - if '-test' in optionlist: - testmode = True - - user_project_path = None - - if len(arguments) == 0: - user_project_path = os.getcwd() - else: - user_project_path = arguments[0] - - if not os.path.isdir(user_project_path): - print('Error: Project path "' + user_project_path + '" does not exist or is not readable.') - sys.exit(1) - - magpath = user_project_path + '/mag' - gdspath = user_project_path + '/gds' - vpath = user_project_path + '/verilog' - - # Check paths - if not os.path.isdir(gdspath): - print('No directory ' + gdspath + ' found (path to GDS).') - sys.exit(1) - - if not os.path.isdir(vpath): - print('No directory ' + vpath + ' found (path to verilog).') - sys.exit(1) - - if not os.path.isdir(magpath): - print('No directory ' + magpath + ' found (path to magic databases).') - sys.exit(1) - - # Parse the user defines verilog file - kvpairs = {} - if os.path.isfile(vpath + '/rtl/user_defines.v'): - with open(vpath + '/rtl/user_defines.v', 'r') as ifile: - infolines = ifile.read().splitlines() - for line in infolines: - tokens = line.split() - if len(tokens) >= 3: - if tokens[0] == '`define': - if tokens[2][0] == '`': - # If definition is nested, substitute value. - tokens[2] = kvpairs[tokens[2]] - kvpairs['`' + tokens[1]] = tokens[2] - else: - print('Error: No user_defines.v file found.') - sys.exit(1) - - # Set additional dictionary entries for the fixed-configuration - # GPIOs 0 to 4. This allows the layout to have the default - # gpio_defaults_block layout, and this script will change it as - # needed. - - kvpairs["`USER_CONFIG_GPIO_0_INIT"] = "13'h1803" - kvpairs["`USER_CONFIG_GPIO_1_INIT"] = "13'h1803" - kvpairs["`USER_CONFIG_GPIO_2_INIT"] = "13'h0403" - kvpairs["`USER_CONFIG_GPIO_3_INIT"] = "13'h0403" - kvpairs["`USER_CONFIG_GPIO_4_INIT"] = "13'h0403" - - # Generate zero and one coordinates for each via - llx_zero = [] - lly_zero = [] - urx_zero = [] - ury_zero = [] - llx_one = [] - lly_one = [] - urx_one = [] - ury_one = [] - - zero_string = [] - one_string = [] - - for i in range(0, 13): - llx_zero = int(via_pos[i][0] * 200) - lly_zero = int(via_pos[i][1] * 200) - urx_zero = llx_zero + 34 - ury_zero = lly_zero + 34 - - llx_one = llx_zero - 138 - lly_one = lly_zero - urx_one = urx_zero - 138 - ury_one = ury_zero - - zero_string.append('rect {:d} {:d} {:d} {:d}'.format(llx_zero, lly_zero, urx_zero, ury_zero)) - one_string.append('rect {:d} {:d} {:d} {:d}'.format(llx_one, lly_one, urx_one, ury_one)) - - # Create new cells for each unique type - print('Step 1: Create new cells for new GPIO default vectors.') - - cellsused = [None] * 38 - - for i in range(5, 38): - config_name = '`USER_CONFIG_GPIO_' + str(i) + '_INIT' - try: - config_value = kvpairs[config_name] - except: - print('No configuration specified for GPIO ' + str(i) + '; skipping.') - continue - - try: - default_str = config_value[-4:] - binval = '{:013b}'.format(int(default_str, 16)) - except: - print('Error: Default value ' + config_value + ' is not a 4-digit hex number; skipping') - continue - - cell_name = 'gpio_defaults_block_' + default_str - mag_file = magpath + '/' + cell_name + '.mag' - cellsused[i] = cell_name - - if not os.path.isfile(mag_file): - # A cell with this set of defaults doesn't exist, so make it - # First read the 0000 cell, then write to mag_path while - # changing the position of vias on the "1" bits - - # Record which bits need to be set - bitflips = [] - for j in range(0, 13): - if binval[12 - j] == '1': - bitflips.append(j) - - with open(magpath + '/gpio_defaults_block.mag', 'r') as ifile: - maglines = ifile.read().splitlines() - outlines = [] - for magline in maglines: - is_flipped = False - for bitflip in bitflips: - if magline == zero_string[bitflip]: - is_flipped = True - break - if is_flipped: - outlines.append(one_string[bitflip]) - else: - outlines.append(magline) - - print('Creating new layout file ' + mag_file) - if testmode: - print('(Test only)') - else: - with open(mag_file, 'w') as ofile: - for outline in outlines: - print(outline, file=ofile) - else: - print('Layout file ' + mag_file + ' already exists and does not need to be generated.') - - print('Step 2: Modify top-level layouts to use the specified defaults.') - - # Create a backup of the caravan and caravel layouts - if not testmode: - shutil.copy(magpath + '/caravel.mag', magpath + '/caravel.mag.bak') - shutil.copy(magpath + '/caravan.mag', magpath + '/caravan.mag.bak') - - if testmode: - print('Test only: Caravel layout:') - with open(magpath + '/caravel.mag', 'r') as ifile: - maglines = ifile.read().splitlines() - outlines = [] - for magline in maglines: - if magline.startswith('use '): - tokens = magline.split() - instname = tokens[2] - if instname.startswith('gpio_defaults_block_'): - gpioidx = instname[20:] - cellname = cellsused[int(gpioidx)] - if cellname: - tokens[1] = cellname - outlines.append(' '.join(tokens)) - if testmode: - print('Replacing line: ' + magline) - print('With: ' + ' '.join(tokens)) - else: - outlines.append(magline) - else: - outlines.append(magline) - - if not testmode: - with open(magpath + '/caravel.mag', 'w') as ofile: - for outline in outlines: - print(outline, file=ofile) - - if testmode: - print('Test only: Caravan layout:') - with open(magpath + '/caravan.mag', 'r') as ifile: - maglines = ifile.read().splitlines() - outlines = [] - for magline in maglines: - if magline.startswith('use '): - tokens = magline.split() - instname = tokens[2] - if instname.startswith('gpio_defaults_block_'): - gpioidx = instname[20:] - cellname = cellsused[int(gpioidx)] - if cellname: - tokens[1] = cellname - outlines.append(' '.join(tokens)) - if testmode: - print('Replacing line: ' + magline) - print('With: ' + ' '.join(tokens)) - else: - outlines.append(magline) - else: - outlines.append(magline) - - if not testmode: - with open(magpath + '/caravan.mag', 'w') as ofile: - for outline in outlines: - print(outline, file=ofile) - - print('Done.') - sys.exit(0) diff --git a/scripts/generate_fill.py b/scripts/generate_fill.py new file mode 100755 index 00000000..c03a9f2d --- /dev/null +++ b/scripts/generate_fill.py @@ -0,0 +1,416 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2020 Efabless Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# SPDX-License-Identifier: Apache-2.0 + +# +# generate_fill.py --- +# +# Run the fill generation on a layout top level. +# + +import sys +import os +import re +import glob +import subprocess +import multiprocessing + +def usage(): + print("Usage:") + print("generate_fill.py [-keep] [-test] [-dist]") + print("") + print("where:") + print(" is a character string of eight hex digits, and") + print(" is the path to the project top level directory.") + print("") + print(" If is not given, then it must exist in the info.yaml file.") + print(" If is not given, then it is assumed to be the cwd.") + print(" If '-keep' is specified, then keep the generation script.") + print(" If '-test' is specified, then create but do not run the generation script.") + print(" If '-dist' is specified, then run distributed (multi-processing).") + + return 0 + +def makegds(file): + # Procedure for multiprocessing run only: Run the distributed processing + # script to load a .mag file of one flattened square area of the layout, + # and run the fill generator to produce a .gds file output from it. + + magpath = os.path.split(file)[0] + filename = os.path.split(file)[1] + + myenv = os.environ.copy() + myenv['MAGTYPE'] = 'mag' + + mproc = subprocess.run(['magic', '-dnull', '-noconsole', + '-rcfile', rcfile, magpath + '/generate_fill_dist.tcl', + filename], + stdin = subprocess.DEVNULL, + stdout = subprocess.PIPE, + stderr = subprocess.PIPE, + cwd = magpath, + env = myenv, + universal_newlines = True) + if mproc.stdout: + for line in mproc.stdout.splitlines(): + print(line) + if mproc.stderr: + print('Error message output from magic:') + for line in mproc.stderr.splitlines(): + print(line) + if mproc.returncode != 0: + print('ERROR: Magic exited with status ' + str(mproc.returncode)) + + +if __name__ == '__main__': + + optionlist = [] + arguments = [] + + debugmode = False + keepmode = False + testmode = False + distmode = False + + for option in sys.argv[1:]: + if option.find('-', 0) == 0: + optionlist.append(option) + else: + arguments.append(option) + + if len(arguments) < 3: + print("Wrong number of arguments given to generate_fill.py.") + usage() + sys.exit(1) + + user_id_value = arguments[0] + project = arguments[1] + user_project_path = arguments[2] + + try: + # Convert to binary + user_id_int = int('0x' + user_id_value, 0) + user_id_bits = '{0:032b}'.format(user_id_int) + + except: + print("User ID not recognized") + usage() + sys.exit(1) + + # if len(arguments) == 0: + # user_project_path = os.getcwd() + # elif len(arguments) == 2: + # user_project_path = arguments[1] + # elif user_project_path == None: + # user_project_path = arguments[0] + # else: + # user_project_path = os.getcwd() + + + if not os.path.isdir(user_project_path): + print('Error: Project path "' + user_project_path + '" does not exist or is not readable.') + sys.exit(1) + + # Check for valid user ID + # if not user_id_value: + # if os.path.isfile(user_project_path + '/info.yaml'): + # with open(user_project_path + '/info.yaml', 'r') as ifile: + # infolines = ifile.read().splitlines() + # for line in infolines: + # kvpair = line.split(':') + # if len(kvpair) == 2: + # key = kvpair[0].strip() + # value = kvpair[1].strip() + # if key == 'project_id': + # user_id_value = value.strip('"\'') + # break + + if user_id_value: + project_with_id = 'caravel_' + user_id_value + else: + print('Error: No project_id found in info.yaml file.') + sys.exit(1) + + if '-debug' in optionlist: + debugmode = True + if '-keep' in optionlist: + keepmode = True + if '-test' in optionlist: + testmode = True + if '-dist' in optionlist: + distmode = True + + magpath = user_project_path + '/mag' + rcfile = magpath + '/.magicrc' + + if not os.path.isfile(rcfile): + rcfile = None + + topdir = user_project_path + gdsdir = topdir + '/gds' + hasgdsdir = True if os.path.isdir(gdsdir) else False + + ofile = open(magpath + '/generate_fill.tcl', 'w') + + print('#!/bin/env wish', file=ofile) + print('drc off', file=ofile) + print('tech unlock *', file=ofile) + print('snap internal', file=ofile) + print('box values 0 0 0 0', file=ofile) + print('box size 700um 700um', file=ofile) + print('set stepbox [box values]', file=ofile) + print('set stepwidth [lindex $stepbox 2]', file=ofile) + print('set stepheight [lindex $stepbox 3]', file=ofile) + print('', file=ofile) + print('set starttime [orig_clock format [orig_clock seconds] -format "%D %T"]', file=ofile) + print('puts stdout "Started: $starttime"', file=ofile) + print('', file=ofile) + # Read the user project from GDS, as there is not necessarily a magic database file + # to go along with this. + # print('gds read ../gds/user_project_wrapper', file=ofile) + # Now read the full caravel project + # print('load ' + project + ' -dereference', file=ofile) + print('gds readonly true', file=ofile) + print('gds rescale false', file=ofile) + print('gds read ../gds/' + project, file=ofile) + print('select top cell', file=ofile) + print('expand', file=ofile) + if not distmode: + print('cif ostyle wafflefill(tiled)', file=ofile) + print('', file=ofile) + print('set fullbox [box values]', file=ofile) + print('set xmax [lindex $fullbox 2]', file=ofile) + print('set xmin [lindex $fullbox 0]', file=ofile) + print('set fullwidth [expr {$xmax - $xmin}]', file=ofile) + print('set xtiles [expr {int(ceil(($fullwidth + 0.0) / $stepwidth))}]', file=ofile) + print('set ymax [lindex $fullbox 3]', file=ofile) + print('set ymin [lindex $fullbox 1]', file=ofile) + print('set fullheight [expr {$ymax - $ymin}]', file=ofile) + print('set ytiles [expr {int(ceil(($fullheight + 0.0) / $stepheight))}]', file=ofile) + print('box size $stepwidth $stepheight', file=ofile) + print('set xbase [lindex $fullbox 0]', file=ofile) + print('set ybase [lindex $fullbox 1]', file=ofile) + print('', file=ofile) + + # Break layout into tiles and process each separately + print('for {set y 0} {$y < $ytiles} {incr y} {', file=ofile) + print(' for {set x 0} {$x < $xtiles} {incr x} {', file=ofile) + print(' set xlo [expr $xbase + $x * $stepwidth]', file=ofile) + print(' set ylo [expr $ybase + $y * $stepheight]', file=ofile) + print(' set xhi [expr $xlo + $stepwidth]', file=ofile) + print(' set yhi [expr $ylo + $stepheight]', file=ofile) + print(' if {$xhi > $fullwidth} {set xhi $fullwidth}', file=ofile) + print(' if {$yhi > $fullheight} {set yhi $fullheight}', file=ofile) + print(' box values $xlo $ylo $xhi $yhi', file=ofile) + # The flattened area must be larger than the fill tile by >1.5um + print(' box grow c 1.6um', file=ofile) + + # Flatten into a cell with a new name + print(' puts stdout "Flattening layout of tile x=$x y=$y. . . "', file=ofile) + print(' flush stdout', file=ofile) + print(' update idletasks', file=ofile) + print(' flatten -dobox -nolabels ' + project_with_id + '_fill_pattern_${x}_$y', file=ofile) + print(' load ' + project_with_id + '_fill_pattern_${x}_$y', file=ofile) + # Remove any GDS_FILE reference (there should not be any?) + print(' property GDS_FILE ""', file=ofile) + # Set boundary using comment layer, to the size of the step box + # This corresponds to the "topbox" rule in the wafflefill(tiled) style + print(' select top cell', file=ofile) + print(' erase comment', file=ofile) + print(' box values $xlo $ylo $xhi $yhi', file=ofile) + print(' paint comment', file=ofile) + + if not distmode: + print(' puts stdout "Writing GDS. . . "', file=ofile) + + print(' flush stdout', file=ofile) + print(' update idletasks', file=ofile) + + if distmode: + print(' writeall force ' + project_with_id + '_fill_pattern_${x}_$y', file=ofile) + else: + print(' gds write ' + project_with_id + '_fill_pattern_${x}_$y.gds', file=ofile) + # Reload project top + print(' load ' + project, file=ofile) + + # Remove last generated cell to save memory + print(' cellname delete ' + project_with_id + '_fill_pattern_${x}_$y', file=ofile) + + print(' }', file=ofile) + print('}', file=ofile) + + if distmode: + print('set ofile [open fill_gen_info.txt w]', file=ofile) + print('puts $ofile "$stepwidth"', file=ofile) + print('puts $ofile "$stepheight"', file=ofile) + print('puts $ofile "$xtiles"', file=ofile) + print('puts $ofile "$ytiles"', file=ofile) + print('puts $ofile "$xbase"', file=ofile) + print('puts $ofile "$ybase"', file=ofile) + print('close $ofile', file=ofile) + print('quit -noprompt', file=ofile) + ofile.close() + + with open(magpath + '/generate_fill_dist.tcl', 'w') as ofile: + print('#!/bin/env wish', file=ofile) + print('drc off', file=ofile) + print('tech unlock *', file=ofile) + print('snap internal', file=ofile) + print('box values 0 0 0 0', file=ofile) + print('set filename [file root [lindex $argv $argc-1]]', file=ofile) + print('load $filename', file=ofile) + print('cif ostyle wafflefill(tiled)', file=ofile) + print('gds write [file root $filename].gds', file=ofile) + print('quit -noprompt', file=ofile) + + ofile = open(magpath + '/generate_fill_final.tcl', 'w') + print('#!/bin/env wish', file=ofile) + print('drc off', file=ofile) + print('tech unlock *', file=ofile) + print('snap internal', file=ofile) + print('box values 0 0 0 0', file=ofile) + + print('set ifile [open fill_gen_info.txt r]', file=ofile) + print('gets $ifile stepwidth', file=ofile) + print('gets $ifile stepheight', file=ofile) + print('gets $ifile xtiles', file=ofile) + print('gets $ifile ytiles', file=ofile) + print('gets $ifile xbase', file=ofile) + print('gets $ifile ybase', file=ofile) + print('close $ifile', file=ofile) + print('cif ostyle wafflefill(tiled)', file=ofile) + + # Now create simple "fake" views of all the tiles. + print('gds readonly true', file=ofile) + print('gds rescale false', file=ofile) + print('for {set y 0} {$y < $ytiles} {incr y} {', file=ofile) + print(' for {set x 0} {$x < $xtiles} {incr x} {', file=ofile) + print(' set xlo [expr $xbase + $x * $stepwidth]', file=ofile) + print(' set ylo [expr $ybase + $y * $stepheight]', file=ofile) + print(' set xhi [expr $xlo + $stepwidth]', file=ofile) + print(' set yhi [expr $ylo + $stepheight]', file=ofile) + print(' load ' + project_with_id + '_fill_pattern_${x}_$y -quiet', file=ofile) + print(' box values $xlo $ylo $xhi $yhi', file=ofile) + print(' paint comment', file=ofile) + print(' property FIXED_BBOX "$xlo $ylo $xhi $yhi"', file=ofile) + print(' property GDS_FILE ' + project_with_id + '_fill_pattern_${x}_${y}.gds', file=ofile) + print(' property GDS_START 0', file=ofile) + print(' }', file=ofile) + print('}', file=ofile) + + # Now tile everything back together + print('load ' + project_with_id + '_fill_pattern -quiet', file=ofile) + print('for {set y 0} {$y < $ytiles} {incr y} {', file=ofile) + print(' for {set x 0} {$x < $xtiles} {incr x} {', file=ofile) + print(' box values 0 0 0 0', file=ofile) + print(' getcell ' + project_with_id + '_fill_pattern_${x}_$y child 0 0', file=ofile) + print(' }', file=ofile) + print('}', file=ofile) + + # And write final GDS + print('puts stdout "Writing final GDS"', file=ofile) + + print('cif *hier write disable', file=ofile) + print('cif *array write disable', file=ofile) + if hasgdsdir: + print('gds write ../gds/' + project_with_id + '_fill_pattern.gds', file=ofile) + else: + print('gds write ' + project_with_id + '_fill_pattern.gds', file=ofile) + print('set endtime [orig_clock format [orig_clock seconds] -format "%D %T"]', file=ofile) + print('puts stdout "Ended: $endtime"', file=ofile) + print('quit -noprompt', file=ofile) + ofile.close() + + myenv = os.environ.copy() + myenv['MAGTYPE'] = 'mag' + + if not testmode: + # Diagnostic + # print('This script will generate file ' + project_with_id + '_fill_pattern.gds') + print('This script will generate files ' + project_with_id + '_fill_pattern_x_y.gds') + print('Now generating fill patterns. This may take. . . quite. . . a while.', flush=True) + mproc = subprocess.run(['magic', '-dnull', '-noconsole', + '-rcfile', rcfile, magpath + '/generate_fill.tcl'], + stdin = subprocess.DEVNULL, + stdout = subprocess.PIPE, + stderr = subprocess.PIPE, + cwd = magpath, + env = myenv, + universal_newlines = True) + if mproc.stdout: + for line in mproc.stdout.splitlines(): + print(line) + if mproc.stderr: + print('Error message output from magic:') + for line in mproc.stderr.splitlines(): + print(line) + if mproc.returncode != 0: + print('ERROR: Magic exited with status ' + str(mproc.returncode)) + + if distmode: + # If using distributed mode, then run magic on each of the generated + # layout files + pool = multiprocessing.Pool() + magfiles = glob.glob(magpath + '/' + project_with_id + '_fill_pattern_*.mag') + # NOTE: Adding 'x' to the end of each filename, or else magic will + # try to read it from the command line as well as passing it as an + # argument to the script. We only want it passed as an argument. + magxfiles = list(item + 'x' for item in magfiles) + pool.map(makegds, magxfiles) + + # If using distributed mode, then remove all of the temporary .mag files + # and then run the final generation script. + for file in magfiles: + os.remove(file) + + mproc = subprocess.run(['magic', '-dnull', '-noconsole', + '-rcfile', rcfile, magpath + '/generate_fill_final.tcl'], + stdin = subprocess.DEVNULL, + stdout = subprocess.PIPE, + stderr = subprocess.PIPE, + cwd = magpath, + env = myenv, + universal_newlines = True) + if mproc.stdout: + for line in mproc.stdout.splitlines(): + print(line) + if mproc.stderr: + print('Error message output from magic:') + for line in mproc.stderr.splitlines(): + print(line) + if mproc.returncode != 0: + print('ERROR: Magic exited with status ' + str(mproc.returncode)) + + if not keepmode: + # Remove fill generation script + os.remove(magpath + '/generate_fill.tcl') + # Remove all individual fill tiles, leaving only the composite GDS. + filelist = os.listdir(magpath) + for file in filelist: + if os.path.splitext(magpath + '/' + file)[1] == '.gds': + if file.startswith(project_with_id + '_fill_pattern_'): + os.remove(magpath + '/' + file) + + if distmode: + os.remove(magpath + '/generate_fill_dist.tcl') + os.remove(magpath + '/generate_fill_final.tcl') + os.remove(magpath + '/fill_gen_info.txt') + if testmode: + magfiles = glob.glob(magpath + '/' + project_with_id + '_fill_pattern_*.mag') + for file in magfiles: + os.remove(file) + + print('Done!') + exit(0) diff --git a/scripts/generate_fill_orig.py b/scripts/generate_fill_orig.py new file mode 100755 index 00000000..3a43a8c5 --- /dev/null +++ b/scripts/generate_fill_orig.py @@ -0,0 +1,268 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: 2020 Efabless Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# SPDX-License-Identifier: Apache-2.0 + +# +# generate_fill_orig.py --- +# +# Run the fill generation on a layout top level. +# This is the older version that does not have a "-dist" option for +# distributed (multiprocessing) operation. +# + +import sys +import os +import re +import subprocess + +def usage(): + print("Usage:") + print("generate_fill_orig.py [] [-keep] [-test]") + print("") + print("where:") + print(" is the path to the project top level directory.") + print("") + print(" If is not given, then it is assumed to be the cwd.") + print(" If '-keep' is specified, then keep the generation script.") + print(" If '-test' is specified, then create but do not run the generation script.") + return 0 + +if __name__ == '__main__': + + optionlist = [] + arguments = [] + + debugmode = False + keepmode = False + testmode = False + + for option in sys.argv[1:]: + if option.find('-', 0) == 0: + optionlist.append(option) + else: + arguments.append(option) + + if len(arguments) > 1: + print("Wrong number of arguments given to generate_fill_orig.py.") + usage() + sys.exit(1) + + if len(arguments) == 1: + user_project_path = arguments[0] + else: + user_project_path = os.getcwd() + + if not os.path.isdir(user_project_path): + print('Error: Project path "' + user_project_path + '" does not exist or is not readable.') + sys.exit(1) + + # Check for valid user ID + user_id_value = None + if os.path.isfile(user_project_path + '/info.yaml'): + with open(user_project_path + '/info.yaml', 'r') as ifile: + infolines = ifile.read().splitlines() + for line in infolines: + kvpair = line.split(':') + if len(kvpair) == 2: + key = kvpair[0].strip() + value = kvpair[1].strip() + if key == 'project_id': + user_id_value = value.strip('"\'') + break + + project = 'caravel' + if user_id_value: + project_with_id = project + '_' + user_id_value + else: + print('Error: No project_id found in info.yaml file.') + sys.exit(1) + + if '-debug' in optionlist: + debugmode = True + if '-keep' in optionlist: + keepmode = True + if '-test' in optionlist: + testmode = True + + magpath = user_project_path + '/mag' + rcfile = magpath + '/.magicrc' + + if not os.path.isfile(rcfile): + rcfile = None + + topdir = user_project_path + gdsdir = topdir + '/gds' + hasgdsdir = True if os.path.isdir(gdsdir) else False + + with open(magpath + '/generate_fill.tcl', 'w') as ofile: + print('#!/bin/env wish', file=ofile) + print('drc off', file=ofile) + print('tech unlock *', file=ofile) + print('snap internal', file=ofile) + print('box values 0 0 0 0', file=ofile) + print('box size 700um 700um', file=ofile) + print('set stepbox [box values]', file=ofile) + print('set stepwidth [lindex $stepbox 2]', file=ofile) + print('set stepheight [lindex $stepbox 3]', file=ofile) + print('', file=ofile) + print('set starttime [orig_clock format [orig_clock seconds] -format "%D %T"]', file=ofile) + print('puts stdout "Started: $starttime"', file=ofile) + print('', file=ofile) + # Read the user project from GDS, as there is not necessarily a magic database file + # to go along with this. + # print('gds read ../gds/user_project_wrapper', file=ofile) + # Now read the full caravel project + # print('load ' + project + ' -dereference', file=ofile) + print('gds readonly true', file=ofile) + print('gds rescale false', file=ofile) + print('gds read ../gds/caravel', file=ofile) + print('select top cell', file=ofile) + print('expand', file=ofile) + print('cif ostyle wafflefill(tiled)', file=ofile) + print('', file=ofile) + print('set fullbox [box values]', file=ofile) + print('set xmax [lindex $fullbox 2]', file=ofile) + print('set xmin [lindex $fullbox 0]', file=ofile) + print('set fullwidth [expr {$xmax - $xmin}]', file=ofile) + print('set xtiles [expr {int(ceil(($fullwidth + 0.0) / $stepwidth))}]', file=ofile) + print('set ymax [lindex $fullbox 3]', file=ofile) + print('set ymin [lindex $fullbox 1]', file=ofile) + print('set fullheight [expr {$ymax - $ymin}]', file=ofile) + print('set ytiles [expr {int(ceil(($fullheight + 0.0) / $stepheight))}]', file=ofile) + print('box size $stepwidth $stepheight', file=ofile) + print('set xbase [lindex $fullbox 0]', file=ofile) + print('set ybase [lindex $fullbox 1]', file=ofile) + print('', file=ofile) + + # Break layout into tiles and process each separately + print('for {set y 0} {$y < $ytiles} {incr y} {', file=ofile) + print(' for {set x 0} {$x < $xtiles} {incr x} {', file=ofile) + print(' set xlo [expr $xbase + $x * $stepwidth]', file=ofile) + print(' set ylo [expr $ybase + $y * $stepheight]', file=ofile) + print(' set xhi [expr $xlo + $stepwidth]', file=ofile) + print(' set yhi [expr $ylo + $stepheight]', file=ofile) + print(' if {$xhi > $fullwidth} {set xhi $fullwidth}', file=ofile) + print(' if {$yhi > $fullheight} {set yhi $fullheight}', file=ofile) + print(' box values $xlo $ylo $xhi $yhi', file=ofile) + # The flattened area must be larger than the fill tile by >1.5um + print(' box grow c 1.6um', file=ofile) + + # Flatten into a cell with a new name + print(' puts stdout "Flattening layout of tile x=$x y=$y. . . "', file=ofile) + print(' flush stdout', file=ofile) + print(' update idletasks', file=ofile) + print(' flatten -dobox -nolabels ' + project_with_id + '_fill_pattern_${x}_$y', file=ofile) + print(' load ' + project_with_id + '_fill_pattern_${x}_$y', file=ofile) + + # Remove any GDS_FILE reference (there should not be any?) + print(' property GDS_FILE ""', file=ofile) + # Set boundary using comment layer, to the size of the step box + # This corresponds to the "topbox" rule in the wafflefill(tiled) style + print(' select top cell', file=ofile) + print(' erase comment', file=ofile) + print(' box values $xlo $ylo $xhi $yhi', file=ofile) + print(' paint comment', file=ofile) + print(' puts stdout "Writing GDS. . . "', file=ofile) + print(' flush stdout', file=ofile) + print(' update idletasks', file=ofile) + print(' gds write ' + project_with_id + '_fill_pattern_${x}_$y.gds', file=ofile) + + # Reload project top + print(' load ' + project, file=ofile) + + # Remove last generated cell to save memory + print(' cellname delete ' + project_with_id + '_fill_pattern_${x}_$y', file=ofile) + + print(' }', file=ofile) + print('}', file=ofile) + + # Now create simple "fake" views of all the tiles. + print('gds readonly true', file=ofile) + print('gds rescale false', file=ofile) + print('for {set y 0} {$y < $ytiles} {incr y} {', file=ofile) + print(' for {set x 0} {$x < $xtiles} {incr x} {', file=ofile) + print(' set xlo [expr $xbase + $x * $stepwidth]', file=ofile) + print(' set ylo [expr $ybase + $y * $stepheight]', file=ofile) + print(' set xhi [expr $xlo + $stepwidth]', file=ofile) + print(' set yhi [expr $ylo + $stepheight]', file=ofile) + print(' load ' + project_with_id + '_fill_pattern_${x}_$y -quiet', file=ofile) + print(' box values $xlo $ylo $xhi $yhi', file=ofile) + print(' paint comment', file=ofile) + print(' property FIXED_BBOX "$xlo $ylo $xhi $yhi"', file=ofile) + print(' property GDS_FILE ' + project_with_id + '_fill_pattern_${x}_${y}.gds', file=ofile) + print(' property GDS_START 0', file=ofile) + print(' }', file=ofile) + print('}', file=ofile) + + # Now tile everything back together + print('load ' + project_with_id + '_fill_pattern -quiet', file=ofile) + print('for {set y 0} {$y < $ytiles} {incr y} {', file=ofile) + print(' for {set x 0} {$x < $xtiles} {incr x} {', file=ofile) + print(' box values 0 0 0 0', file=ofile) + print(' getcell ' + project_with_id + '_fill_pattern_${x}_$y child 0 0', file=ofile) + print(' }', file=ofile) + print('}', file=ofile) + + # And write final GDS + print('puts stdout "Writing final GDS"', file=ofile) + + print('cif *hier write disable', file=ofile) + print('cif *array write disable', file=ofile) + if hasgdsdir: + print('gds write ../gds/' + project_with_id + '_fill_pattern.gds', file=ofile) + else: + print('gds write ' + project_with_id + '_fill_pattern.gds', file=ofile) + print('set endtime [orig_clock format [orig_clock seconds] -format "%D %T"]', file=ofile) + print('puts stdout "Ended: $endtime"', file=ofile) + print('quit -noprompt', file=ofile) + + myenv = os.environ.copy() + myenv['MAGTYPE'] = 'mag' + + if not testmode: + # Diagnostic + # print('This script will generate file ' + project_with_id + '_fill_pattern.gds') + print('This script will generate files ' + project_with_id + '_fill_pattern_x_y.gds') + print('Now generating fill patterns. This may take. . . quite. . . a while.', flush=True) + mproc = subprocess.run(['magic', '-dnull', '-noconsole', + '-rcfile', rcfile, magpath + '/generate_fill.tcl'], + stdin = subprocess.DEVNULL, + stdout = subprocess.PIPE, + stderr = subprocess.PIPE, + cwd = magpath, + env = myenv, + universal_newlines = True) + if mproc.stdout: + for line in mproc.stdout.splitlines(): + print(line) + if mproc.stderr: + print('Error message output from magic:') + for line in mproc.stderr.splitlines(): + print(line) + if mproc.returncode != 0: + print('ERROR: Magic exited with status ' + str(mproc.returncode)) + + if not keepmode: + # Remove fill generation script + os.remove(magpath + '/generate_fill.tcl') + # Remove all individual fill tiles, leaving only the composite GDS. + filelist = os.listdir(magpath) + for file in filelist: + if os.path.splitext(magpath + '/' + file)[1] == '.gds': + if file.startswith(project + '_fill_pattern_'): + os.remove(magpath + '/' + file) + + print('Done!') + exit(0) diff --git a/scripts/make_bump_bonds.tcl b/scripts/make_bump_bonds.tcl new file mode 100755 index 00000000..ade81d6b --- /dev/null +++ b/scripts/make_bump_bonds.tcl @@ -0,0 +1,702 @@ +# SPDX-FileCopyrightText: 2020 Efabless Corporation +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# SPDX-License-Identifier: Apache-2.0 +#---------------------------------------------------------------------- +# Assumes running magic -T micross using the micross technology file +# from the open_pdks installation of sky130A +#---------------------------------------------------------------------- +# bump bond pitch is 500um. Bump diameter is set by the technology + +namespace path {::tcl::mathop ::tcl::mathfunc} + +if {[catch {set PDKPATH $env(PDKPATH)}]} { + set PDKPATH "$::env(PDK_ROOT)/sky130A" +} + +source $PDKPATH/libs.tech/magic/current/bump_bond_generator/bump_bond.tcl + +# Caravel dimensions, in microns +set chipwidth 3588 +set chipheight 5188 + +set halfwidth [/ $chipwidth 2] +set halfheight [/ $chipheight 2] + +set columns 6 +set rows 10 + +set bump_pitch 500 + +set llx [- $halfwidth [* [- [/ $columns 2] 0.5] $bump_pitch]] +set lly [- $halfheight [* [- [/ $rows 2] 0.5] $bump_pitch]] + +# Create a new cell +load caravel_bump_bond -quiet + +# Build the bump cells +make_bump_bond 0 +make_bump_bond 45 + +# View the whole chip during generation. This is not strictly +# necessary, but looks nice! +snap internal +box values 0 0 ${chipwidth}um ${chipheight}um +paint glass +view +erase glass +box values 0 0 0 0 +grid 250um 250um 45um 95um + +# Starting from the bottom left-hand corner and scanning across and up, +# these are the orientations of the bump bond pad tapers: +set tapers {} +lappend tapers 180 225 270 270 270 270 +lappend tapers 180 135 225 270 0 0 +lappend tapers 180 135 135 270 315 0 +lappend tapers 180 135 135 315 315 0 +lappend tapers 135 135 0 180 315 0 +lappend tapers 180 135 0 180 315 0 +lappend tapers 180 135 180 315 315 0 +lappend tapers 180 180 135 45 315 0 +lappend tapers 135 135 135 45 45 45 +lappend tapers 90 90 90 90 45 90 + +box values 0 0 0 0 +set t 0 +for {set y 0} {$y < $rows} {incr y} { + for {set x 0} {$x < $columns} {incr x} { + set xpos [+ $llx [* $x $bump_pitch]] + set ypos [+ $lly [* $y $bump_pitch]] + draw_bump_bond $xpos $ypos [lindex $tapers $t] + incr t + } +} + +# The pad at E6 has wires exiting two sides, so put another pad down +# at the other orientation. +set y 4 +set x 4 +set xpos [+ $llx [* $x $bump_pitch]] +set ypos [+ $lly [* $y $bump_pitch]] +draw_bump_bond $xpos $ypos 180 + +select top cell +expand + +# These are the pad Y positions on the left side from bottom to top + +set leftpads {} +lappend leftpads 377.5 588.5 950.5 1166.5 1382.5 1598.5 1814.5 +lappend leftpads 2030.5 2241.5 2452.5 2668.5 2884.5 3100.5 +lappend leftpads 3316.5 3532.5 3748.5 3964.5 4175.5 4386.5 4597.5 4813.5 + +# These are the pad X positions on the top side from left to right + +set toppads {} +lappend toppads 423.5 680.5 937.5 1194.5 1452.5 1704.5 1961.5 2406.5 +lappend toppads 2663.5 2915.5 3172.5 + +# These are the pad Y positions on the right side from bottom to top + +set rightpads {} +lappend rightpads 537.5 763.5 988.5 1214.5 1439.5 1664.5 1890.5 +lappend rightpads 2115.5 2336.5 2556.5 2776.5 3002.5 3227.5 3453.5 +lappend rightpads 3678.5 3903.5 4129.5 4349.5 4575.5 4795.5 + +# These are the pad X positions on the bottom side from left to right + +set bottompads {} +lappend bottompads 431.5 700.5 969.5 1243.5 1512.5 1786.5 2060.5 +lappend bottompads 2334.5 2608.5 2882.5 3151.5 + +set leftpadx 64.6 +set rightpadx 3523.78 +set bottompady 64.6 +set toppady 5123.78 + +set xpos $leftpadx +for {set y 0} {$y < [llength $leftpads]} {incr y} { + set ypos [lindex $leftpads $y] + draw_pad_bond $xpos $ypos +} + +set ypos $toppady +for {set x 0} {$x < [llength $toppads]} {incr x} { + set xpos [lindex $toppads $x] + draw_pad_bond $xpos $ypos +} + +set xpos $rightpadx +for {set y 0} {$y < [llength $rightpads]} {incr y} { + set ypos [lindex $rightpads $y] + draw_pad_bond $xpos $ypos +} + +set ypos $bottompady +for {set x 0} {$x < [llength $bottompads]} {incr x} { + set xpos [lindex $bottompads $x] + draw_pad_bond $xpos $ypos +} + +# Now route between the wirebond pads and the bump bond pads +# routes start centered on the wirebond pad and align to grid points +# on a 1/2 ball pitch, although positions do not need to be on +# integer values. The overlaid grid starts 1/2 pitch to the left +# and below the center of the bottom left bump bond. Grid columns +# are numbered 0 to 12, and grid rows are numbered 0 to 20. To +# convert to a micron unit coordinate, use the to_grid procedure +# defined below. + +set gridllx [- $llx 250.0] +set gridlly [- $lly 250.0] +set gridpitchx 250.0 +set gridpitchy 250.0 + +proc to_grid {x y} { + global gridllx gridlly + set coords [] + catch {lappend coords [+ $gridllx [* 250.0 $x]]} + catch {lappend coords [+ $gridlly [* 250.0 $y]]} + return $coords +} + +# Detailed routing, scanning left to right and from bottom to top. +# (This really needs to be automated. . .) + +set wire_width 40.0 + +# A10 vccd +set coords [list $leftpadx [lindex $leftpads 0]] +lappend coords {*}[to_grid -0.8 1] +lappend coords {*}[to_grid 1 1] +draw_pad_route $coords $wire_width + +# B10 resetb +set coords [list [lindex $bottompads 1] $bottompady] +lappend coords {*}[to_grid 1.9 0.2] +lappend coords {*}[to_grid 2.2 0.2] +lappend coords {*}[to_grid 3 1] +draw_pad_route $coords $wire_width + +# C10 flash csb +set coords [list [lindex $bottompads 4] $bottompady] +lappend coords {*}[to_grid 5 0] +lappend coords {*}[to_grid 5 1] +draw_pad_route $coords $wire_width + +# D10 flash io0 +set coords [list [lindex $bottompads 6] $bottompady] +lappend coords {*}[to_grid 7 0] +lappend coords {*}[to_grid 7 1] +draw_pad_route $coords $wire_width + +# E10 gpio +set coords [list [lindex $bottompads 8] $bottompady] +lappend coords {*}[to_grid 9 0.2] +lappend coords {*}[to_grid 9 1] +draw_pad_route $coords $wire_width + +# F10 vdda +set coords [list [lindex $bottompads 10] $bottompady] +lappend coords {*}[to_grid 11 0.3] +lappend coords {*}[to_grid 11 1] +draw_pad_route $coords $wire_width + +# A9 mprj_io[37] +set coords [list $leftpadx [lindex $leftpads 2]] +lappend coords {*}[to_grid -0.5 3] +lappend coords {*}[to_grid 1 3] +draw_pad_route $coords $wire_width + +# B9 mprj_io[36] +set coords [list $leftpadx [lindex $leftpads 3]] +lappend coords {*}[to_grid -0.6 4] +lappend coords {*}[to_grid 2 4] +lappend coords {*}[to_grid 3 3] +draw_pad_route $coords $wire_width + +# C9 clock +set coords [list [lindex $bottompads 2] $bottompady] +lappend coords {*}[to_grid 3 0.2] +lappend coords {*}[to_grid 3.4 0.2] +lappend coords {*}[to_grid 3.8 0.6] +lappend coords {*}[to_grid 3.8 1.6] +lappend coords {*}[to_grid 4.5 2.3] +lappend coords {*}[to_grid 4.5 2.5] +lappend coords {*}[to_grid 5 3] +draw_pad_route $coords $wire_width + +# D9 flash io1 +set coords [list [lindex $bottompads 7] $bottompady] +lappend coords {*}[to_grid 8 0.1] +lappend coords {*}[to_grid 8 1.3] +lappend coords {*}[to_grid 7 2.3] +lappend coords {*}[to_grid 7 3] +draw_pad_route $coords $wire_width + +# E9 mprj_io[1]/SDO +set coords [list $rightpadx [lindex $rightpads 1]] +lappend coords {*}[to_grid 12.4 2.2] +lappend coords {*}[to_grid 10.5 2.2] +lappend coords {*}[to_grid 9.7 3] +lappend coords {*}[to_grid 9 3] +draw_pad_route $coords $wire_width + +# F9 mprj_io[2]/SDI +set coords [list $rightpadx [lindex $rightpads 2]] +lappend coords {*}[to_grid 12.3 3] +lappend coords {*}[to_grid 11 3] +draw_pad_route $coords $wire_width + +# A8 mprj_io[35] +set coords [list $leftpadx [lindex $leftpads 4]] +lappend coords {*}[to_grid -0.7 5] +lappend coords {*}[to_grid 1 5] +draw_pad_route $coords $wire_width + +# B8 mprj_io[34] +set coords [list $leftpadx [lindex $leftpads 5]] +lappend coords {*}[to_grid -0.7 5.8] +lappend coords {*}[to_grid 2.2 5.8] +lappend coords {*}[to_grid 3 5] +draw_pad_route $coords $wire_width + +# C8 mprj_io[33] +set coords [list $leftpadx [lindex $leftpads 6]] +lappend coords {*}[to_grid -0.3 6.2] +lappend coords {*}[to_grid 3.8 6.2] +lappend coords {*}[to_grid 5 5] +draw_pad_route $coords $wire_width + +# D8 flash clk +set coords [list [lindex $bottompads 5] $bottompady] +lappend coords {*}[to_grid 6 0] +lappend coords {*}[to_grid 6 1] +lappend coords {*}[to_grid 6.2 1.2] +lappend coords {*}[to_grid 6.2 3.5] +lappend coords {*}[to_grid 7 4.3] +lappend coords {*}[to_grid 7 5] +draw_pad_route $coords $wire_width + +# E8 mprj_io[3]/CSB +set coords [list $rightpadx [lindex $rightpads 3]] +lappend coords {*}[to_grid 12.4 4] +lappend coords {*}[to_grid 10 4] +lappend coords {*}[to_grid 9 5] +draw_pad_route $coords $wire_width + +# F8 mrpj_io[4]/SCK +set coords [list $rightpadx [lindex $rightpads 4]] +lappend coords {*}[to_grid 12.5 5] +lappend coords {*}[to_grid 11 5] +draw_pad_route $coords $wire_width + +# A7 mrpj_io[32] +set coords [list $leftpadx [lindex $leftpads 7]] +lappend coords {*}[to_grid -0.2 7] +lappend coords {*}[to_grid 1 7] +draw_pad_route $coords $wire_width + +# B7 vssd2 +set coords [list $leftpadx [lindex $leftpads 8]] +lappend coords {*}[to_grid -0.1 7.8] +lappend coords {*}[to_grid 2.2 7.8] +lappend coords {*}[to_grid 3 7] +draw_pad_route $coords $wire_width + +# C7 vdda2 +set coords [list $leftpadx [lindex $leftpads 9]] +lappend coords {*}[to_grid 0.3 8.2] +lappend coords {*}[to_grid 2.3 8.2] +lappend coords {*}[to_grid 2.5 8] +lappend coords {*}[to_grid 4 8] +lappend coords {*}[to_grid 5 7] +draw_pad_route $coords $wire_width + +# D7 mrpj_io[0]/JTAG +set coords [list $rightpadx [lindex $rightpads 0]] +lappend coords {*}[to_grid 12.8 1.8] +lappend coords {*}[to_grid 10.2 1.8] +lappend coords {*}[to_grid 9.8 2.2] +lappend coords {*}[to_grid 8.6 2.2] +lappend coords {*}[to_grid 8.2 2.6] +lappend coords {*}[to_grid 8.2 5.8] +lappend coords {*}[to_grid 7 7] +draw_pad_route $coords $wire_width + +# E7 mrpj_io[5]/ser_rx +set coords [list $rightpadx [lindex $rightpads 5]] +lappend coords {*}[to_grid 12.6 6] +lappend coords {*}[to_grid 10 6] +lappend coords {*}[to_grid 9 7] +draw_pad_route $coords $wire_width + +# F7 mprj_io[6]/ser_tx +set coords [list $rightpadx [lindex $rightpads 6]] +lappend coords {*}[to_grid 12.7 7] +lappend coords {*}[to_grid 11 7] +draw_pad_route $coords $wire_width + +# A6 mprj_io[31] +set coords [list $leftpadx [lindex $leftpads 10]] +lappend coords {*}[to_grid -0.3 10.3] +lappend coords {*}[to_grid 1 9] +draw_pad_route $coords $wire_width + +# B6 mprj_io[30] +set coords [list $leftpadx [lindex $leftpads 11]] +lappend coords {*}[to_grid -0.5 10.8] +lappend coords {*}[to_grid -0.3 10.8] +lappend coords {*}[to_grid 0.5 10] +lappend coords {*}[to_grid 2 10] +lappend coords {*}[to_grid 3 9] +draw_pad_route $coords $wire_width + +# C6 vssio/vssa/vssd: Connects to D6, D5, C5 +set coords [to_grid 5 9] +lappend coords {*}[to_grid 5.65 9] +lappend coords {*}[to_grid 5.85 9.2] +lappend coords {*}[to_grid 6 9.2] +draw_pad_route $coords $wire_width + +# D6 vssio/vssa/vssd +set coords [to_grid 7 9] +lappend coords {*}[to_grid 6.35 9] +lappend coords {*}[to_grid 6.15 8.8] +lappend coords {*}[to_grid 6 8.8] +draw_pad_route $coords $wire_width + +# D6 vssio/vssa/vssd also goes to: +set coords [list [lindex $bottompads 0] $bottompady] +lappend coords {*}[to_grid 0.9 0.2] +lappend coords {*}[to_grid 1.3 0.2] +lappend coords {*}[to_grid 2 0.9] +lappend coords {*}[to_grid 2 1.5] +lappend coords {*}[to_grid 2.3 1.8] +lappend coords {*}[to_grid 3.5 1.8] +lappend coords {*}[to_grid 4.2 2.5] +lappend coords {*}[to_grid 4.2 3.5] +lappend coords {*}[to_grid 4.5 3.8] +lappend coords {*}[to_grid 5.3 3.8] +lappend coords {*}[to_grid 5.8 3.3] +lappend coords {*}[to_grid 5.8 2.5] +lappend coords {*}[to_grid 5.3 2] +lappend coords {*}[to_grid 4.8 2] +lappend coords {*}[to_grid 4.2 1.4] +lappend coords {*}[to_grid 4.2 0.3] +lappend coords {*}[list [lindex $bottompads 3] $bottompady] +draw_pad_route $coords $wire_width + +# D6 vssio/vssa/vssd also goes to: +set coords [list [lindex $bottompads 9] $bottompady] +lappend coords {*}[to_grid 10 0.3] +lappend coords {*}[to_grid 10 1.4] +lappend coords {*}[to_grid 9.6 1.8] +lappend coords {*}[to_grid 8.5 1.8] +lappend coords {*}[to_grid 7.8 2.5] +lappend coords {*}[to_grid 7.8 5.5] +lappend coords {*}[to_grid 7.3 6] +lappend coords {*}[to_grid 6.2 6] +draw_pad_route $coords $wire_width + +# D6 vssio/vssa/vssd also goes to: +set coords [list [lindex $toppads 5] $toppady] +lappend coords {*}[to_grid 6 19.7] +lappend coords {*}[to_grid 6 16] +lappend coords {*}[to_grid 5.8 15.8] +lappend coords {*}[to_grid 5.8 12.2] +lappend coords {*}[to_grid 6 12] +lappend coords {*}[to_grid 6 8] +lappend coords {*}[to_grid 6.2 7.8] +lappend coords {*}[to_grid 6.2 4.3] +lappend coords {*}[to_grid 5.5 3.6] +draw_pad_route $coords $wire_width + +# E6 vssa1 +set coords [list $rightpadx [lindex $rightpads 7]] +lappend coords {*}[to_grid 12.8 8] +lappend coords {*}[to_grid 10 8] +lappend coords {*}[to_grid 9 9] +draw_pad_route $coords $wire_width + +# E6 vssa1 also goes to +set coords [list [lindex $toppads 9] $toppady] +lappend coords {*}[to_grid 10 19.5] +lappend coords {*}[to_grid 10 18.5] +lappend coords {*}[to_grid 9.5 18] +lappend coords {*}[to_grid 8.5 18] +lappend coords {*}[to_grid 8 17.5] +lappend coords {*}[to_grid 8 16.5] +lappend coords {*}[to_grid 7.5 16] +lappend coords {*}[to_grid 6.7 16] +lappend coords {*}[to_grid 6.2 15.5] +lappend coords {*}[to_grid 6.2 12.6] +lappend coords {*}[to_grid 6.7 12] +lappend coords {*}[to_grid 7.3 12] +lappend coords {*}[to_grid 7.8 11.5] +lappend coords {*}[to_grid 7.8 10.2] +lappend coords {*}[to_grid 8 10] +lappend coords {*}[to_grid 8 9.3] +lappend coords {*}[to_grid 8.3 9] +lappend coords {*}[to_grid 9 9] +draw_pad_route $coords $wire_width + +# F6 vssd1 +set coords [list $rightpadx [lindex $rightpads 8]] +lappend coords {*}[to_grid 12.9 9] +lappend coords {*}[to_grid 11 9] +draw_pad_route $coords $wire_width + +# A5 mprj_io[29] +set coords [list $leftpadx [lindex $leftpads 12]] +lappend coords {*}[to_grid 0.2 11] +lappend coords {*}[to_grid 1 11] +draw_pad_route $coords $wire_width + +# B5 mprj_io[28] +set coords [list $leftpadx [lindex $leftpads 13]] +lappend coords {*}[to_grid 0 12] +lappend coords {*}[to_grid 2 12] +lappend coords {*}[to_grid 3 11] +draw_pad_route $coords $wire_width + +# C5 vssio/vssa/vssd : Connects to D6, C6, D5 +set coords [to_grid 5 11] +lappend coords {*}[to_grid 5.65 11] +lappend coords {*}[to_grid 5.85 11.2] +lappend coords {*}[to_grid 6 11.2] +draw_pad_route $coords $wire_width + +# D5 vssio/vssa/vssd : Connects to D6, C6, C5 +set coords [to_grid 7 11] +lappend coords {*}[to_grid 6.35 11] +lappend coords {*}[to_grid 6.15 10.8] +lappend coords {*}[to_grid 6 10.8] +draw_pad_route $coords $wire_width + +# E5 mprj_io[7]/irq +set coords [list $rightpadx [lindex $rightpads 10]] +lappend coords {*}[to_grid 12.4 10.2] +lappend coords {*}[to_grid 9.8 10.2] +lappend coords {*}[to_grid 9 11] +draw_pad_route $coords $wire_width + +# F5 mprj_io[8]/flash2 csb +set coords [list $rightpadx [lindex $rightpads 11]] +lappend coords {*}[to_grid 12.3 11] +lappend coords {*}[to_grid 11 11] +draw_pad_route $coords $wire_width + +# A4 mprj_io[27] +set coords [list $leftpadx [lindex $leftpads 14]] +lappend coords {*}[to_grid -0.1 13] +lappend coords {*}[to_grid 1 13] +draw_pad_route $coords $wire_width + +# B4 mprj_io[26] +set coords [list $leftpadx [lindex $leftpads 15]] +lappend coords {*}[to_grid -0.2 14] +lappend coords {*}[to_grid 2 14] +lappend coords {*}[to_grid 3 13] +draw_pad_route $coords $wire_width + +# C4 vddio +set coords [list $leftpadx [lindex $leftpads 1]] +lappend coords {*}[to_grid -0.8 2] +lappend coords {*}[to_grid 1.8 2] +lappend coords {*}[to_grid 2 2.2] +lappend coords {*}[to_grid 3.3 2.2] +lappend coords {*}[to_grid 3.8 2.7] +lappend coords {*}[to_grid 3.8 3.7] +lappend coords {*}[to_grid 4.3 4.2] +lappend coords {*}[to_grid 5.3 4.2] +lappend coords {*}[to_grid 5.8 4.7] +lappend coords {*}[to_grid 5.8 7.4] +lappend coords {*}[to_grid 5.2 8] +lappend coords {*}[to_grid 4.7 8] +lappend coords {*}[to_grid 4 8.7] +lappend coords {*}[to_grid 4 13] +draw_pad_route $coords $wire_width + +# C4 vddio is also: +set coords [list $leftpadx [lindex $leftpads 18]] +lappend coords {*}[to_grid 0.1 16.2] +lappend coords {*}[to_grid 1.6 16.2] +lappend coords {*}[to_grid 2 15.8] +lappend coords {*}[to_grid 3.4 15.8] +lappend coords {*}[to_grid 4 15.2] +lappend coords {*}[to_grid 4 13] +lappend coords {*}[to_grid 5 13] +draw_pad_route $coords $wire_width + +# D4 vdda1 +set coords [list $rightpadx [lindex $rightpads 9]] +lappend coords {*}[to_grid 12.8 9.8] +lappend coords {*}[to_grid 9.7 9.8] +lappend coords {*}[to_grid 9.5 10] +lappend coords {*}[to_grid 8.8 10] +lappend coords {*}[to_grid 8.2 10.6] +lappend coords {*}[to_grid 8.2 11.8] +lappend coords {*}[to_grid 7 13] +draw_pad_route $coords $wire_width + +# D4 vdda1 is also: +set coords [list $rightpadx [lindex $rightpads 16]] +lappend coords {*}[to_grid 12.6 15.8] +lappend coords {*}[to_grid 8.4 15.8] +lappend coords {*}[to_grid 8 15.4] +lappend coords {*}[to_grid 8 12.4] +lappend coords {*}[to_grid 7.8 12.2] +draw_pad_route $coords $wire_width + +# E4 mprj_io[9]/flash2 sck +set coords [list $rightpadx [lindex $rightpads 12]] +lappend coords {*}[to_grid 12.4 12] +lappend coords {*}[to_grid 10 12] +lappend coords {*}[to_grid 9 13] +draw_pad_route $coords $wire_width + +# F4 mprj_io[10]/flash2 io0 +set coords [list $rightpadx [lindex $rightpads 13]] +lappend coords {*}[to_grid 12.5 13] +lappend coords {*}[to_grid 11 13] +draw_pad_route $coords $wire_width + +# A3 mprj_io[25] +set coords [list $leftpadx [lindex $leftpads 16]] +lappend coords {*}[to_grid -0.4 15] +lappend coords {*}[to_grid 1 15] +draw_pad_route $coords $wire_width + +# B3 vssa2 +set coords [list $leftpadx [lindex $leftpads 17]] +lappend coords {*}[to_grid -0.4 15.8] +lappend coords {*}[to_grid 0 15.8] +lappend coords {*}[to_grid 1.3 15.8] +lappend coords {*}[to_grid 2.2 15] +lappend coords {*}[to_grid 3 15] +draw_pad_route $coords $wire_width + +# C3 mprj_io[24] +set coords [list $leftpadx [lindex $leftpads 20]] +lappend coords {*}[to_grid 0 18] +lappend coords {*}[to_grid 1.5 18] +lappend coords {*}[to_grid 2 17.5] +lappend coords {*}[to_grid 2 16.5] +lappend coords {*}[to_grid 2.3 16.2] +lappend coords {*}[to_grid 3.8 16.2] +lappend coords {*}[to_grid 5 15] +draw_pad_route $coords $wire_width + +# D3 mprj_io[13] +set coords [list $rightpadx [lindex $rightpads 17]] +lappend coords {*}[to_grid 12 16.2] +lappend coords {*}[to_grid 8.2 16.2] +lappend coords {*}[to_grid 7 15] +draw_pad_route $coords $wire_width + +# E3 mprj_io[11]/flash2 io1 +set coords [list $rightpadx [lindex $rightpads 14]] +lappend coords {*}[to_grid 12.6 14] +lappend coords {*}[to_grid 10 14] +lappend coords {*}[to_grid 9 15] +draw_pad_route $coords $wire_width + +# F3 mprj_io[12] +set coords [list $rightpadx [lindex $rightpads 15]] +lappend coords {*}[to_grid 12.7 15] +lappend coords {*}[to_grid 11 15] +draw_pad_route $coords $wire_width + +# A2 vccd2 +set coords [list $leftpadx [lindex $leftpads 19]] +lappend coords {*}[to_grid -0.4 17.5] +lappend coords {*}[to_grid 0.5 17.5] +lappend coords {*}[to_grid 1 17] +draw_pad_route $coords $wire_width + +# B2 mprj_io[22] +set coords [list [lindex $toppads 1] $toppady] +lappend coords {*}[to_grid 2 19.7] +lappend coords {*}[to_grid 2 18] +lappend coords {*}[to_grid 3 17] +draw_pad_route $coords $wire_width + +# C2 mprj_io[20] +set coords [list [lindex $toppads 3] $toppady] +lappend coords {*}[to_grid 4 19.7] +lappend coords {*}[to_grid 4 18] +lappend coords {*}[to_grid 5 17] +draw_pad_route $coords $wire_width + +# D2 mprj_io[17] +set coords [list [lindex $toppads 7] $toppady] +lappend coords {*}[to_grid 8 19.7] +lappend coords {*}[to_grid 8 18] +lappend coords {*}[to_grid 7 17] +draw_pad_route $coords $wire_width + +# E2 mprj_io[14] +set coords [list $rightpadx [lindex $rightpads 19]] +lappend coords {*}[to_grid 12.6 18.5] +lappend coords {*}[to_grid 12 18.5] +lappend coords {*}[to_grid 11.5 18] +lappend coords {*}[to_grid 10 18] +lappend coords {*}[to_grid 9 17] +draw_pad_route $coords $wire_width + +# F2 vccd1 +set coords [list $rightpadx [lindex $rightpads 18]] +lappend coords {*}[to_grid 12.5 17.5] +lappend coords {*}[to_grid 11.5 17.5] +lappend coords {*}[to_grid 11 17] +draw_pad_route $coords $wire_width + +# A1 mprj_io[23] +set coords [list [lindex $toppads 0] $toppady] +lappend coords {*}[to_grid 1 19.7] +lappend coords {*}[to_grid 1 19] +draw_pad_route $coords $wire_width + +# B1 mprj_io[21] +set coords [list [lindex $toppads 2] $toppady] +lappend coords {*}[to_grid 3 19.7] +lappend coords {*}[to_grid 3 19] +draw_pad_route $coords $wire_width + +# C1 mprj_io[19] +set coords [list [lindex $toppads 4] $toppady] +lappend coords {*}[to_grid 5 19.7] +lappend coords {*}[to_grid 5 19] +draw_pad_route $coords $wire_width + +# D1 mrpj_io[18] +set coords [list [lindex $toppads 6] $toppady] +lappend coords {*}[to_grid 7 19.7] +lappend coords {*}[to_grid 7 19] +draw_pad_route $coords $wire_width + +# E1 mprj_io[16] +set coords [list [lindex $toppads 8] $toppady] +lappend coords {*}[to_grid 9.5 20] +lappend coords {*}[to_grid 9.5 19.5] +lappend coords {*}[to_grid 9 19] +draw_pad_route $coords $wire_width + +# F1 mprj_io[15] +set coords [list [lindex $toppads 10] $toppady] +lappend coords {*}[to_grid 11 19.7] +lappend coords {*}[to_grid 11 19] +draw_pad_route $coords $wire_width +