From 0a09f3b5ac3921f4dca9937dd49896b1a9facca8 Mon Sep 17 00:00:00 2001 From: jeffdi Date: Wed, 1 Dec 2021 11:03:45 -0800 Subject: [PATCH] added missing scripts --- gds/gpio_defaults_block_0402.gds | Bin 51120 -> 0 bytes gds/gpio_defaults_block_1403.gds | Bin 51120 -> 0 bytes gds/gpio_defaults_block_1803.gds | Bin 51120 -> 0 bytes scripts/check_density.py | 616 ++++++++++++++++++++++++++ scripts/compositor.py | 253 +++++++++++ scripts/count_lvs.py | 121 +++++ scripts/create-caravel-diagram.py | 126 ++++++ scripts/gen_gpio_defaults_orig.py | 318 -------------- scripts/generate_fill.py | 416 ++++++++++++++++++ scripts/generate_fill_orig.py | 268 ++++++++++++ scripts/make_bump_bonds.tcl | 702 ++++++++++++++++++++++++++++++ 11 files changed, 2502 insertions(+), 318 deletions(-) delete mode 100644 gds/gpio_defaults_block_0402.gds delete mode 100644 gds/gpio_defaults_block_1403.gds delete mode 100644 gds/gpio_defaults_block_1803.gds create mode 100755 scripts/check_density.py create mode 100755 scripts/compositor.py create mode 100755 scripts/count_lvs.py create mode 100755 scripts/create-caravel-diagram.py delete mode 100755 scripts/gen_gpio_defaults_orig.py create mode 100755 scripts/generate_fill.py create mode 100755 scripts/generate_fill_orig.py create mode 100755 scripts/make_bump_bonds.tcl diff --git a/gds/gpio_defaults_block_0402.gds b/gds/gpio_defaults_block_0402.gds deleted file mode 100644 index d026e80947b677890a1c8896166d83f383b0f93e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51120 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 diff --git a/gds/gpio_defaults_block_1403.gds b/gds/gpio_defaults_block_1403.gds deleted file mode 100644 index 53cb8a9cfc6e505df3d173333b470cc05f460603..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 51120 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] [-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 +