added missing scripts

This commit is contained in:
jeffdi 2021-12-01 11:03:45 -08:00
parent 0f90c813ed
commit 0a09f3b5ac
11 changed files with 2502 additions and 318 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

616
scripts/check_density.py Executable file
View File

@ -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 [<path_to_project>] [-keep]")
print("")
print("where:")
print(" <path_to_project> is the path to the project top level directory.")
print("")
print(" If <path_to_project> 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)

253
scripts/compositor.py Executable file
View File

@ -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 <user_id_value> <project> <path_to_project> <path_to_mag_dir> <path_to_gds_dir [-keep]")
print("")
print("where:")
print(" <user_id_value> is a character string of eight hex digits, and")
print(" <path_to_project> is the path to the project top level directory.")
print(" <path_to_mag_dir> is the path to the mag directory.")
print(" <path_to_gds_dir> is the path to the gds directory.")
print("")
print(" If <user_id_value> is not given, then it must exist in the info.yaml file.")
print(" If <path_to_project> is not given, then it is assumed to be the cwd.")
print(" If <path_to_mag_dir> is not given, then it is assumed to be the <path_to_project>/tmp.")
print(" If <path_to_gds_dir> is not given, then it is assumed to be the <path_to_project>/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 <project_with_id>.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)

121
scripts/count_lvs.py Executable file
View File

@ -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))

126
scripts/create-caravel-diagram.py Executable file
View File

@ -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))

View File

@ -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 [<path_to_project>]')
print('')
print('where:')
print(' <path_to_project> is the path to the project top level directory.')
print('')
print(' If <path_to_project> 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(' <path_to_project>.')
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)

416
scripts/generate_fill.py Executable file
View File

@ -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 <user_id_value> <project> <path_to_project> [-keep] [-test] [-dist]")
print("")
print("where:")
print(" <user_id_value> is a character string of eight hex digits, and")
print(" <path_to_project> is the path to the project top level directory.")
print("")
print(" If <user_id_value> is not given, then it must exist in the info.yaml file.")
print(" If <path_to_project> 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)

268
scripts/generate_fill_orig.py Executable file
View File

@ -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 [<path_to_project>] [-keep] [-test]")
print("")
print("where:")
print(" <path_to_project> is the path to the project top level directory.")
print("")
print(" If <path_to_project> 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)

702
scripts/make_bump_bonds.tcl Executable file
View File

@ -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