Modified the density check script to correct an error (multiple
errors, actually) in pro-rating the partial tile areas on the
right and top sides of a layout. Modified the density check
script and the fill generation script to operate on both .mag
and .gds files. Modified the fill generation script to use
zlib compression when writing the final file (which can be
huge). Modified all scripts (fill generation, density check,
and antenna check) to use PDK_ROOT when available, and to make
a sensible search for the startup script file for magic.
diff --git a/VERSION b/VERSION
index 0b869e6..a9d3998 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.0.512
+1.0.513
diff --git a/sky130/custom/scripts/check_antenna.py b/sky130/custom/scripts/check_antenna.py
index e7eb89f..cc2eb56 100755
--- a/sky130/custom/scripts/check_antenna.py
+++ b/sky130/custom/scripts/check_antenna.py
@@ -66,6 +66,9 @@
elif 'PDK_PATH' in myenv:
rcpathroot = myenv['PDKPATH'] + '/libs.tech/magic'
rcfile = glob.glob(rcpathroot + '/*.magicrc')[0]
+ elif 'PDK_ROOT' in myenv and 'PDK' in myenv:
+ rcpathroot = myenv['PDK_ROOT'] + '/' + myenv['PDK'] + '/libs.tech/magic'
+ rcfile = glob.glob(rcpathroot + '/*.magicrc')[0]
else:
print('Error: Cannot get magic rcfile for the technology!')
return
diff --git a/sky130/custom/scripts/check_density.py b/sky130/custom/scripts/check_density.py
index 3affd97..72bf2f9 100755
--- a/sky130/custom/scripts/check_density.py
+++ b/sky130/custom/scripts/check_density.py
@@ -23,20 +23,21 @@
import sys
import os
import re
+import glob
import select
import subprocess
def usage():
print("Usage:")
- print("check_density.py [<gds_file_name>] [-keep]")
+ print("check_density.py [<layout_file_name>] [-keep]")
print("")
print("where:")
- print(" <gds_file_name> is the path to the .gds file to be checked.")
+ print(" <layout_file_name> is the path to the .gds or .mag file to be checked.")
print("")
print(" If '-keep' is specified, then keep the check script.")
+ print(" If '-debug' is specified, then print diagnostic information.")
return 0
-
if __name__ == '__main__':
optionlist = []
@@ -56,60 +57,97 @@
usage()
sys.exit(0)
- relative_path=arguments[0]
-
- gdspath = os.getcwd()+'/'+os.path.split(relative_path)[0]+'/'
- if gdspath == '':
- gdspath = os.getcwd()
-
- gds_filepath = os.path.split(relative_path)[1]
-
- if os.path.splitext(gds_filepath)[1] != '.gds':
- if os.path.splitext(gds_filepath)[1] == '':
- gds_filepath += '.gds'
- else:
- print('Error: Project is not a GDS file!')
- sys.exit(1)
-
- gdsname = os.path.split(gds_filepath)[1]
- gdsroot = os.path.splitext(gdsname)[0]
-
- # Check for valid path to the GDS file
-
- if not os.path.isdir(gdspath):
- print('Error: Project path "' + gds_filepath + '" does not exist or is not readable.')
- sys.exit(1)
-
- if not os.path.isfile(gdspath+gds_filepath):
- print('Error: Project "' + gdspath+gds_filepath + '" does not exist or is not readable.')
- sys.exit(1)
+ # Process options
if '-debug' in optionlist:
debugmode = True
+ print('Running in debug mode.')
if '-keep' in optionlist:
keepmode = True
+ if debugmode:
+ print('Keeping all files after running.')
+ elif debugmode:
+ print('Temporary files will be removed after running.')
- # NOTE: There should be some attempt to find the installed PDK magicrc file
- # if there is no mag/ directory.
-
-
- # Searching for rcfile
-
- rcfile_paths=[gdspath+'/.magicrc','/$PDK_PATH/libs.tech/magic/TECHNAME.magicrc','/usr/share/pdk/TECHNAME/libs.tech/magic/TECHNAME.magicrc']
-
- rcfile=''
-
- for rc_path in rcfile_paths:
- if os.path.isfile(rc_path):
- rcfile=rc_path
- break
-
- if rcfile=='':
- print('Error: .magicrc file not found.')
+ # Find layout from command-line argument
+
+ user_project_path = arguments[0]
+
+ if os.path.split(user_project_path)[0] == '':
+ layoutpath = os.getcwd()
+ else:
+ layoutpath = os.getcwd() + '/' + os.path.split(user_project_path)[0]
+
+ # Use split() not os.path.splitext() to capture double-dot extensions
+ # like "layout.gds.gz".
+
+ project = user_project_path.split(os.extsep, 1)
+
+ if len(project) == 1:
+ # No file extension given; figure it out
+ layoutfiles = glob.glob(layoutpath + '/' + user_project_path + '.*')
+ if len(layoutfiles) == 1:
+ proj_extension = '.' + layoutfiles[0].split(os.extsep, 1)[1]
+ user_project_path = layoutfiles[0]
+ elif len(layoutfiles) == 0:
+ if debugmode:
+ print('No matching files found for ' + layoutpath + '/' + user_project_path + '.*')
+ print('Error: Project is not a magic database or GDS file!')
+ sys.exit(1)
+ else:
+ print('Error: Project name is ambiguous!')
+ sys.exit(1)
+ else:
+ proj_extension = '.' + project[1]
+
+ is_mag = False
+ is_gds = False
+
+ if proj_extension == '.mag' or proj_extension == '.mag.gz':
+ is_mag = True
+ elif proj_extension == '.gds' or proj_extension == '.gds.gz':
+ is_gds = True
+ else:
+ if debugmode:
+ print('Unknown extension ' + proj_extension + ' in filename.')
+ print('Error: Project is not a magic database or GDS file!')
sys.exit(1)
+ if not os.path.isfile(user_project_path):
+ print('Error: Project "' + user_project_path + '" does not exist or is not readable.')
+ sys.exit(1)
+
+ # The path where the fill generation script resides should be the same
+ # path where the magic startup script resides, for the same PDK
+ scriptpath = os.path.dirname(os.path.realpath(__file__))
- with open(gdspath + '/check_density.tcl', 'w') as ofile:
+ # Search for a magic startup script. Order of precedence:
+ # 1. PDK_ROOT environment variable
+ # 2. Local .magicrc
+ # 3. The location of this script
+
+ if os.environ.get('PDK'):
+ pdk_name = os.environ.get('PDK')
+ else:
+ pdk_name = 'sky130A'
+
+ if os.environ.get('PDK_ROOT'):
+ rcfile_path = os.environ.get('PDK_ROOT') + '/' + pdk_name + '/libs.tech/magic/' + pdk_name + '.magicrc'
+ elif os.path.isfile(layoutpath + '/.magicrc'):
+ rcfile_path = layoutpath + '/.magicrc'
+ elif os.path.isfile(scriptpath + '/' + pdk_name + '.magicrc'):
+ rcfile_path = scriptpath + '/' + pdk_name + '.magicrc'
+ else:
+ print('Unknown path to magic startup script. Please set $PDK_ROOT')
+ sys.exit(1)
+
+ project_file = os.path.split(user_project_path)[1]
+ project = project_file.split(os.extsep, 1)[0]
+
+ # Create the Tcl script to run in magic to check local density across
+ # stepped regions.
+
+ with open(layoutpath + '/check_density.tcl', 'w') as ofile:
print('#!/bin/env wish', file=ofile)
print('crashbackups stop', file=ofile)
print('drc off', file=ofile)
@@ -121,15 +159,16 @@
print('flush stdout', file=ofile)
print('update idletasks', file=ofile)
- # Read GDS file
- print('gds readonly true', file=ofile)
- print('gds rescale false', file=ofile)
- print('gds read ' + gds_filepath, file=ofile)
- print('', file=ofile)
+ if is_gds:
+ # Read GDS file
+ print('gds readonly true', file=ofile)
+ print('gds rescale false', file=ofile)
+ print('gds read ' + project_file, file=ofile)
+ print('', file=ofile)
# NOTE: This assumes that the name of the GDS file is the name of the
# topmost cell (which should be passed as an option)
- print('load ' + gdsroot, file=ofile)
+ print('load ' + project, file=ofile)
print('', file=ofile)
print('set midtime [orig_clock format [orig_clock seconds] -format "%D %T"]', file=ofile)
@@ -153,6 +192,8 @@
print('select top cell', file=ofile)
print('expand', file=ofile)
print('set fullbox [box values]', file=ofile)
+ # Override with FIXED_BBOX, if it is defined
+ print('catch {set fullbox [property FIXED_BBOX]}', 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)
@@ -171,10 +212,10 @@
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('set xfrac [expr {1.0 - ($xtiles * $stepsizex - $fullwidth + 0.0) / $stepsizex}]', file=ofile)
+ print('set yfrac [expr {1.0 - ($ytiles * $stepsizey - $fullheight + 0.0) / $stepsizey}]', file=ofile)
- # If the last row/column fraction is zero, then set to 1
+ # If the last row/column fraction is zero, then set to 1 (might never happen?)
print('if {$xfrac == 0.0} {set xfrac 1.0}', file=ofile)
print('if {$yfrac == 0.0} {set yfrac 1.0}', file=ofile)
@@ -221,7 +262,7 @@
print(' flush stdout', file=ofile)
print(' update idletasks', file=ofile)
- print(' load ' + gdsroot, file=ofile)
+ print(' load ' + project, file=ofile)
print(' cellname delete tile', file=ofile)
print(' }', file=ofile)
@@ -236,14 +277,20 @@
myenv = os.environ.copy()
myenv['MAGTYPE'] = 'mag'
- print('Running density checks on file ' + gds_filepath, flush=True)
+ print('Running density checks on file ' + user_project_path, flush=True)
- mproc = subprocess.Popen(['magic', '-dnull', '-noconsole',
- '-rcfile', rcfile, gdspath + '/check_density.tcl'],
+ magic_run_opts = [
+ 'magic',
+ '-dnull',
+ '-noconsole',
+ '-rcfile', rcfile_path,
+ layoutpath + '/check_density.tcl']
+
+ mproc = subprocess.Popen(magic_run_opts,
stdin = subprocess.DEVNULL,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
- cwd = gdspath,
+ cwd = layoutpath,
env = myenv,
universal_newlines = True)
@@ -307,6 +354,8 @@
for line in dlines:
dpair = line.split(':')
+ if debugmode:
+ print('Magic output line: ' + line)
if len(dpair) == 2:
layer = dpair[0]
try:
@@ -349,40 +398,52 @@
total_tiles = (ytiles - 9) * (xtiles - 9)
print('')
- print('Density results (total tiles = ' + str(total_tiles) + '):')
+ print('Stepped area density results (total tiles = ' + str(total_tiles) + '):')
# 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)
+ print('Side adjustment = ' + '{:.3f}'.format(xfrac))
+ print('Top adjustment = ' + '{:.3f}'.format(yfrac))
- corneradjust = 81.0 + (9.0 * xfrac) + (9.0 * yfrac) + (xfrac * yfrac)
-
- print('Side adjustment = ' + str(sideadjust))
- print('Top adjustment = ' + str(topadjust))
- print('Corner adjustment = ' + str(corneradjust))
+ if debugmode:
+ with open('tile_densities.txt', 'w') as dfile:
+ print(str(fomfill), file=dfile)
+ print(str(polyfill), file=dfile)
+ print(str(lifill), file=dfile)
+ print(str(met1fill), file=dfile)
+ print(str(met2fill), file=dfile)
+ print(str(met3fill), file=dfile)
+ print(str(met4fill), file=dfile)
+ print(str(met5fill), file=dfile)
print('')
print('FOM Density:')
for y in range(0, ytiles - 9):
if y == ytiles - 10:
- atotal = topadjust
+ locyfrac = yfrac
else:
- atotal = 100.0
+ locyfrac = 1.0
for x in range(0, xtiles - 9):
if x == xtiles - 10:
- if y == ytiles - 10:
- atotal = corneradjust
- else:
- atotal = sideadjust
+ locxfrac = xfrac
+ else:
+ locxfrac = 1.0
+
fomaccum = 0
- for w in range(y, y + 10):
+ atotal = 81.0 + 9.0 * locxfrac + 9.0 * locyfrac + locxfrac * locyfrac
+
+ for w in range(y, y + 9):
base = xtiles * w + x
- fomaccum += sum(fomfill[base : base + 10])
+ fomaccum += sum(fomfill[base : base + 9])
+ fomaccum += fomfill[base + 9] * locxfrac
+ base = xtiles * (y + 9) + x
+ fomaccum += sum(fomfill[base : base + 9]) * locyfrac
+ fomaccum += fomfill[base + 9] * locxfrac * locyfrac
fomaccum /= atotal
- print('Tile (' + str(x) + ', ' + str(y) + '): ' + str(fomaccum))
+ fomstr = "{:.3f}".format(fomaccum)
+ print('Tile (' + str(x) + ', ' + str(y) + '): ' + fomstr)
if fomaccum < 0.33:
print('***Error: FOM Density < 33%')
elif fomaccum > 0.57:
@@ -392,43 +453,57 @@
print('POLY Density:')
for y in range(0, ytiles - 9):
if y == ytiles - 10:
- atotal = topadjust
+ locyfrac = yfrac
else:
- atotal = 100.0
+ locyfrac = 1.0
for x in range(0, xtiles - 9):
if x == xtiles - 10:
- if y == ytiles - 10:
- atotal = corneradjust
- else:
- atotal = sideadjust
+ locxfrac = xfrac
+ else:
+ locxfrac = 1.0
+
polyaccum = 0
- for w in range(y, y + 10):
+ atotal = 81.0 + 9.0 * locxfrac + 9.0 * locyfrac + locxfrac * locyfrac
+
+ for w in range(y, y + 9):
base = xtiles * w + x
- polyaccum += sum(polyfill[base : base + 10])
+ polyaccum += sum(polyfill[base : base + 9])
+ polyaccum += polyfill[base + 9] * locxfrac
+ base = xtiles * (y + 9) + x
+ polyaccum += sum(polyfill[base : base + 9]) * locyfrac
+ polyaccum += polyfill[base + 9] * locxfrac * locyfrac
polyaccum /= atotal
- print('Tile (' + str(x) + ', ' + str(y) + '): ' + str(polyaccum))
+ polystr = "{:.3f}".format(polyaccum)
+ print('Tile (' + str(x) + ', ' + str(y) + '): ' + polystr)
print('')
print('LI Density:')
for y in range(0, ytiles - 9):
if y == ytiles - 10:
- atotal = topadjust
+ locyfrac = yfrac
else:
- atotal = 100.0
+ locyfrac = 1.0
for x in range(0, xtiles - 9):
if x == xtiles - 10:
- if y == ytiles - 10:
- atotal = corneradjust
- else:
- atotal = sideadjust
+ locxfrac = xfrac
+ else:
+ locxfrac = 1.0
+
liaccum = 0
- for w in range(y, y + 10):
+ atotal = 81.0 + 9.0 * locxfrac + 9.0 * locyfrac + locxfrac * locyfrac
+
+ for w in range(y, y + 9):
base = xtiles * w + x
- liaccum += sum(lifill[base : base + 10])
+ liaccum += sum(lifill[base : base + 9])
+ liaccum += lifill[base + 9] * locxfrac
+ base = xtiles * (y + 9) + x
+ liaccum += sum(lifill[base : base + 9]) * locyfrac
+ liaccum += lifill[base + 9] * locxfrac * locyfrac
liaccum /= atotal
- print('Tile (' + str(x) + ', ' + str(y) + '): ' + str(liaccum))
+ listr = "{:.3f}".format(liaccum)
+ print('Tile (' + str(x) + ', ' + str(y) + '): ' + listr)
if liaccum < 0.35:
print('***Error: LI Density < 35%')
elif liaccum > 0.60:
@@ -438,22 +513,29 @@
print('MET1 Density:')
for y in range(0, ytiles - 9):
if y == ytiles - 10:
- atotal = topadjust
+ locyfrac = yfrac
else:
- atotal = 100.0
+ locyfrac = 1.0
for x in range(0, xtiles - 9):
if x == xtiles - 10:
- if y == ytiles - 10:
- atotal = corneradjust
- else:
- atotal = sideadjust
+ locxfrac = xfrac
+ else:
+ locxfrac = 1.0
+
met1accum = 0
- for w in range(y, y + 10):
+ atotal = 81.0 + 9.0 * locxfrac + 9.0 * locyfrac + locxfrac * locyfrac
+
+ for w in range(y, y + 9):
base = xtiles * w + x
- met1accum += sum(met1fill[base : base + 10])
+ met1accum += sum(met1fill[base : base + 9])
+ met1accum += met1fill[base + 9] * locxfrac
+ base = xtiles * (y + 9) + x
+ met1accum += sum(met1fill[base : base + 9]) * locyfrac
+ met1accum += met1fill[base + 9] * locxfrac * locyfrac
met1accum /= atotal
- print('Tile (' + str(x) + ', ' + str(y) + '): ' + str(met1accum))
+ met1str = "{:.3f}".format(met1accum)
+ print('Tile (' + str(x) + ', ' + str(y) + '): ' + met1str)
if met1accum < 0.35:
print('***Error: MET1 Density < 35%')
elif met1accum > 0.60:
@@ -463,22 +545,29 @@
print('MET2 Density:')
for y in range(0, ytiles - 9):
if y == ytiles - 10:
- atotal = topadjust
+ locyfrac = yfrac
else:
- atotal = 100.0
+ locyfrac = 1.0
for x in range(0, xtiles - 9):
if x == xtiles - 10:
- if y == ytiles - 10:
- atotal = corneradjust
- else:
- atotal = sideadjust
+ locxfrac = xfrac
+ else:
+ locxfrac = 1.0
+
met2accum = 0
- for w in range(y, y + 10):
+ atotal = 81.0 + 9.0 * locxfrac + 9.0 * locyfrac + locxfrac * locyfrac
+
+ for w in range(y, y + 9):
base = xtiles * w + x
- met2accum += sum(met2fill[base : base + 10])
+ met2accum += sum(met2fill[base : base + 9])
+ met2accum += met2fill[base + 9] * locxfrac
+ base = xtiles * (y + 9) + x
+ met2accum += sum(met2fill[base : base + 9]) * locyfrac
+ met2accum += met2fill[base + 9] * locxfrac * locyfrac
met2accum /= atotal
- print('Tile (' + str(x) + ', ' + str(y) + '): ' + str(met2accum))
+ met2str = "{:.3f}".format(met2accum)
+ print('Tile (' + str(x) + ', ' + str(y) + '): ' + met2str)
if met2accum < 0.35:
print('***Error: MET2 Density < 35%')
elif met2accum > 0.60:
@@ -488,22 +577,29 @@
print('MET3 Density:')
for y in range(0, ytiles - 9):
if y == ytiles - 10:
- atotal = topadjust
+ locyfrac = yfrac
else:
- atotal = 100.0
+ locyfrac = 1.0
for x in range(0, xtiles - 9):
if x == xtiles - 10:
- if y == ytiles - 10:
- atotal = corneradjust
- else:
- atotal = sideadjust
+ locxfrac = xfrac
+ else:
+ locxfrac = 1.0
+
met3accum = 0
- for w in range(y, y + 10):
+ atotal = 81.0 + 9.0 * locxfrac + 9.0 * locyfrac + locxfrac * locyfrac
+
+ for w in range(y, y + 9):
base = xtiles * w + x
- met3accum += sum(met3fill[base : base + 10])
+ met3accum += sum(met3fill[base : base + 9])
+ met3accum += met3fill[base + 9] * locxfrac
+ base = xtiles * (y + 9) + x
+ met3accum += sum(met3fill[base : base + 9]) * locyfrac
+ met3accum += met3fill[base + 9] * locxfrac * locyfrac
met3accum /= atotal
- print('Tile (' + str(x) + ', ' + str(y) + '): ' + str(met3accum))
+ met3str = "{:.3f}".format(met3accum)
+ print('Tile (' + str(x) + ', ' + str(y) + '): ' + met3str)
if met3accum < 0.35:
print('***Error: MET3 Density < 35%')
elif met3accum > 0.60:
@@ -513,22 +609,30 @@
print('MET4 Density:')
for y in range(0, ytiles - 9):
if y == ytiles - 10:
- atotal = topadjust
+ locyfrac = yfrac
else:
- atotal = 100.0
+ locyfrac = 1.0
+
for x in range(0, xtiles - 9):
if x == xtiles - 10:
- if y == ytiles - 10:
- atotal = corneradjust
- else:
- atotal = sideadjust
+ locxfrac = xfrac
+ else:
+ locxfrac = 1.0
+
met4accum = 0
- for w in range(y, y + 10):
+ atotal = 81.0 + 9.0 * locxfrac + 9.0 * locyfrac + locxfrac * locyfrac
+
+ for w in range(y, y + 9):
base = xtiles * w + x
- met4accum += sum(met4fill[base : base + 10])
+ met4accum += sum(met4fill[base : base + 9])
+ met4accum += met4fill[base + 9] * locxfrac
+ base = xtiles * (y + 9) + x
+ met4accum += sum(met4fill[base : base + 9]) * locyfrac
+ met4accum += met4fill[base + 9] * locxfrac * locyfrac
met4accum /= atotal
- print('Tile (' + str(x) + ', ' + str(y) + '): ' + str(met4accum))
+ met4str = "{:.3f}".format(met4accum)
+ print('Tile (' + str(x) + ', ' + str(y) + '): ' + met4str)
if met4accum < 0.35:
print('***Error: MET4 Density < 35%')
elif met4accum > 0.60:
@@ -538,95 +642,182 @@
print('MET5 Density:')
for y in range(0, ytiles - 9):
if y == ytiles - 10:
- atotal = topadjust
+ locyfrac = yfrac
else:
- atotal = 100.0
+ locyfrac = 1.0
for x in range(0, xtiles - 9):
if x == xtiles - 10:
- if y == ytiles - 10:
- atotal = corneradjust
- else:
- atotal = sideadjust
+ locxfrac = xfrac
+ else:
+ locxfrac = 1.0
+
met5accum = 0
- for w in range(y, y + 10):
+ atotal = 81.0 + 9.0 * locxfrac + 9.0 * locyfrac + locxfrac * locyfrac
+
+ for w in range(y, y + 9):
base = xtiles * w + x
- met5accum += sum(met5fill[base : base + 10])
+ met5accum += sum(met5fill[base : base + 9])
+ met5accum += met5fill[base + 9] * locxfrac
+ base = xtiles * (y + 9) + x
+ met5accum += sum(met5fill[base : base + 9]) * locyfrac
+ met5accum += met5fill[base + 9] * locxfrac * locyfrac
met5accum /= atotal
- print('Tile (' + str(x) + ', ' + str(y) + '): ' + str(met5accum))
+ met5str = "{:.3f}".format(met5accum)
+ print('Tile (' + str(x) + ', ' + str(y) + '): ' + met5str)
if met5accum < 0.45:
print('***Error: MET5 Density < 45%')
elif met5accum > 0.76:
print('***Error: MET5 Density > 76%')
print('')
- print('Whole-chip density results:')
+ print('Whole-chip (global) density results:')
atotal = ((xtiles - 1.0) * (ytiles - 1.0)) + ((ytiles - 1.0) * xfrac) + ((xtiles - 1.0) * yfrac) + (xfrac * yfrac)
- fomaccum = sum(fomfill) / atotal
+ fomaccum = 0
+ for y in range(0, ytiles - 1):
+ base = xtiles * y
+ fomaccum += sum(fomfill[base:base + xtiles - 1])
+ fomaccum += fomfill[base + xtiles - 1] * xfrac
+ base = xtiles * (ytiles - 1)
+ fomaccum += sum(fomfill[base:base + xtiles - 1]) * yfrac
+ fomaccum += fomfill[base + xtiles - 1] * xfrac * yfrac
+
+ fomaccum /= atotal
+ fomstr = "{:.3f}".format(fomaccum)
print('')
- print('FOM Density: ' + str(fomaccum))
+ print('FOM Density: ' + fomstr)
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))
+ polyaccum = 0
+ for y in range(0, ytiles - 1):
+ base = xtiles * y
+ polyaccum += sum(polyfill[base:base + xtiles - 1])
+ polyaccum += polyfill[base + xtiles - 1] * xfrac
+ base = xtiles * (ytiles - 1)
+ polyaccum += sum(polyfill[base:base + xtiles - 1]) * yfrac
+ polyaccum += polyfill[base + xtiles - 1] * xfrac * yfrac
- liaccum = sum(lifill) / atotal
+ polyaccum /= atotal
+ polystr = "{:.3f}".format(polyaccum)
print('')
- print('LI Density: ' + str(liaccum))
+ print('POLY Density: ' + polystr)
+
+ liaccum = 0
+ for y in range(0, ytiles - 1):
+ base = xtiles * y
+ liaccum += sum(lifill[base:base + xtiles - 1])
+ liaccum += lifill[base + xtiles - 1] * xfrac
+ base = xtiles * (ytiles - 1)
+ liaccum += sum(lifill[base:base + xtiles - 1]) * yfrac
+ liaccum += lifill[base + xtiles - 1] * xfrac * yfrac
+
+ liaccum /= atotal
+ listr = "{:.3f}".format(liaccum)
+ print('')
+ print('LI Density: ' + listr)
if liaccum < 0.35:
print('***Error: LI Density < 35%')
elif liaccum > 0.60:
print('***Error: LI Density > 60%')
- met1accum = sum(met1fill) / atotal
+ met1accum = 0
+ for y in range(0, ytiles - 1):
+ base = xtiles * y
+ met1accum += sum(met1fill[base:base + xtiles - 1])
+ met1accum += met1fill[base + xtiles - 1] * xfrac
+ base = xtiles * (ytiles - 1)
+ met1accum += sum(met1fill[base:base + xtiles - 1]) * yfrac
+ met1accum += met1fill[base + xtiles - 1] * xfrac * yfrac
+
+ met1accum /= atotal
+ met1str = "{:.3f}".format(met1accum)
print('')
- print('MET1 Density: ' + str(met1accum))
+ print('MET1 Density: ' + met1str)
if met1accum < 0.35:
print('***Error: MET1 Density < 35%')
elif met1accum > 0.60:
print('***Error: MET1 Density > 60%')
- met2accum = sum(met2fill) / atotal
+ met2accum = 0
+ for y in range(0, ytiles - 1):
+ base = xtiles * y
+ met2accum += sum(met2fill[base:base + xtiles - 1])
+ met2accum += met2fill[base + xtiles - 1] * xfrac
+ base = xtiles * (ytiles - 1)
+ met2accum += sum(met2fill[base:base + xtiles - 1]) * yfrac
+ met2accum += met2fill[base + xtiles - 1] * xfrac * yfrac
+
+ met2accum /= atotal
+ met2str = "{:.3f}".format(met2accum)
print('')
- print('MET2 Density: ' + str(met2accum))
+ print('MET2 Density: ' + met2str)
if met2accum < 0.35:
print('***Error: MET2 Density < 35%')
elif met2accum > 0.60:
print('***Error: MET2 Density > 60%')
- met3accum = sum(met3fill) / atotal
+ met3accum = 0
+ for y in range(0, ytiles - 1):
+ base = xtiles * y
+ met3accum += sum(met3fill[base:base + xtiles - 1])
+ met3accum += met3fill[base + xtiles - 1] * xfrac
+ base = xtiles * (ytiles - 1)
+ met3accum += sum(met3fill[base:base + xtiles - 1]) * yfrac
+ met3accum += met3fill[base + xtiles - 1] * xfrac * yfrac
+
+ met3accum /= atotal
+ met3str = "{:.3f}".format(met3accum)
print('')
- print('MET3 Density: ' + str(met3accum))
+ print('MET3 Density: ' + met3str)
if met3accum < 0.35:
print('***Error: MET3 Density < 35%')
elif met3accum > 0.60:
print('***Error: MET3 Density > 60%')
- met4accum = sum(met4fill) / atotal
+ met4accum = 0
+ for y in range(0, ytiles - 1):
+ base = xtiles * y
+ met4accum += sum(met4fill[base:base + xtiles - 1])
+ met4accum += met4fill[base + xtiles - 1] * xfrac
+ base = xtiles * (ytiles - 1)
+ met4accum += sum(met4fill[base:base + xtiles - 1]) * yfrac
+ met4accum += met4fill[base + xtiles - 1] * xfrac * yfrac
+
+ met4accum /= atotal
+ met4str = "{:.3f}".format(met4accum)
print('')
- print('MET4 Density: ' + str(met4accum))
+ print('MET4 Density: ' + met4str)
if met4accum < 0.35:
print('***Error: MET4 Density < 35%')
elif met4accum > 0.60:
print('***Error: MET4 Density > 60%')
- met5accum = sum(met5fill) / atotal
+ met5accum = 0
+ for y in range(0, ytiles - 1):
+ base = xtiles * y
+ met5accum += sum(met5fill[base:base + xtiles - 1])
+ met5accum += met5fill[base + xtiles - 1] * xfrac
+ base = xtiles * (ytiles - 1)
+ met5accum += sum(met5fill[base:base + xtiles - 1]) * yfrac
+ met5accum += met5fill[base + xtiles - 1] * xfrac * yfrac
+
+ met5accum /= atotal
+ met5str = "{:.3f}".format(met5accum)
print('')
- print('MET5 Density: ' + str(met5accum))
+ print('MET5 Density: ' + met5str)
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(gdspath + '/check_density.tcl'):
- os.remove(gdspath + '/check_density.tcl')
+ if os.path.isfile(layoutpath + '/check_density.tcl'):
+ os.remove(layoutpath + '/check_density.tcl')
print('')
print('Done!')
diff --git a/sky130/custom/scripts/generate_fill.py b/sky130/custom/scripts/generate_fill.py
index 38ecc39..1d08dad 100755
--- a/sky130/custom/scripts/generate_fill.py
+++ b/sky130/custom/scripts/generate_fill.py
@@ -24,6 +24,7 @@
import os
import re
import glob
+import functools
import subprocess
import multiprocessing
@@ -32,31 +33,37 @@
print("generate_fill.py <layout_name> [-keep] [-test] [-dist]")
print("")
print("where:")
- print(" <layout_name> is the path to the .mag file to be filled.")
+ print(" <layout_name> is the path to the .mag or .gds file to be filled.")
print("")
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):
+def makegds(file, rcfile):
# Procedure for multiprocessing 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]
+ layoutpath = 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],
+ magic_run_opts = [
+ 'magic',
+ '-dnull',
+ '-noconsole',
+ '-rcfile', rcfile,
+ 'generate_fill_dist.tcl',
+ filename]
+
+ mproc = subprocess.run(magic_run_opts,
stdin = subprocess.DEVNULL,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
- cwd = magpath,
+ cwd = layoutpath,
env = myenv,
universal_newlines = True)
if mproc.stdout:
@@ -91,57 +98,111 @@
usage()
sys.exit(1)
+ # Process options
+
+ if '-debug' in optionlist:
+ debugmode = True
+ print('Running in debug mode.')
+ if '-keep' in optionlist:
+ keepmode = True
+ if debugmode:
+ print('Keeping all files after running.')
+ elif debugmode:
+ print('Files other than final layout will be removed after running.')
+ if '-test' in optionlist:
+ testmode = True
+ if debugmode:
+ print('Running in test mode: No output files will be created.')
+ if '-dist' in optionlist:
+ distmode = True
+ if debugmode:
+ print('Running in distributed (multi-processing) mode.')
+ elif debugmode:
+ print('Running in single-processor mode.')
+
+ # Find layout from command-line argument
+
user_project_path = arguments[0]
- magpath = os.getcwd()+'/'+os.path.split(user_project_path)[0]
- if magpath == '':
- magpath = os.getcwd()
-
- if os.path.splitext(user_project_path)[1] != '.mag':
- if os.path.splitext(user_project_path)[1] == '':
- user_project_path += '.mag'
- else:
- print('Error: Project is not a magic database .mag file!')
+ if os.path.split(user_project_path)[0] == '':
+ layoutpath = os.getcwd()
+ else:
+ layoutpath = os.getcwd() + '/' + os.path.split(user_project_path)[0]
+
+ # Use os.extsep, not os.path.splitext(), because gzipped files have
+ # multiple periods (e.g., "layout.gds.gz")
+
+ project = user_project_path.split(os.extsep, 1)
+
+ if len(project) == 1:
+ # No file extension given; figure it out
+ layoutfiles = glob.glob(user_project_path + '.*')
+ if len(layoutfiles) == 1:
+ proj_extension = '.' + layoutfiles[0].split(os.extsep, 1)[1]
+ user_project_path = layoutfiles[0]
+ elif len(layoutfiles) == 0:
+ print('Error: Project is not a magic database or GDS file!')
sys.exit(1)
+ else:
+ print('Error: Project name is ambiguous!')
+ sys.exit(1)
+ else:
+ proj_extension = '.' + project[1]
+
+ is_mag = False
+ is_gds = False
+
+ if proj_extension == '.mag' or proj_extension == '.mag.gz':
+ is_mag = True
+ elif proj_extension == '.gds' or proj_extension == '.gds.gz':
+ is_gds = True
+ else:
+ print('Error: Project is not a magic database or GDS file!')
+ sys.exit(1)
if not os.path.isfile(user_project_path):
print('Error: Project "' + user_project_path + '" does not exist or is not readable.')
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
+ # The path where the fill generation script resides should be the same
+ # path where the magic startup script resides, for the same PDK
+ scriptpath = os.path.dirname(os.path.realpath(__file__))
- # Searching for rcfile
-
- rcfile_paths=[magpath+'/.magicrc','/$PDK_PATH/libs.tech/magic/TECHNAME.magicrc','/usr/share/pdk/TECHNAME/libs.tech/magic/TECHNAME.magicrc']
-
- rcfile=''
-
- for rc_path in rcfile_paths:
- if os.path.isfile(rc_path):
- rcfile=rc_path
- break
-
- if rcfile=='':
- print('Error: .magicrc file not found.')
+ # Search for magic startup script. Order of precedence:
+ # 1. PDK_ROOT environment variable
+ # 2. Local .magicrc
+ # 3. The location of this script
+
+ if os.environ.get('PDK'):
+ pdk_name = os.environ.get('PDK')
+ else:
+ pdk_name = 'sky130A'
+
+ if os.environ.get('PDK_ROOT'):
+ rcfile_path = os.environ.get('PDK_ROOT') + '/' + pdk_name + '/libs.tech/magic/' + pdk_name + '.magicrc'
+ elif os.path.isfile(layoutpath + '/.magicrc'):
+ rcfile_path = layoutpath + '/.magicrc'
+ elif os.path.isfile(scriptpath + '/' + pdk_name + '.magicrc'):
+ rcfile_path = scriptpath + '/' + pdk_name + '.magicrc'
+ else:
+ print('Unknown path to magic startup script. Please set $PDK_ROOT')
sys.exit(1)
-
- project = os.path.splitext(os.path.split(user_project_path)[1])[0]
- topdir = os.path.split(magpath)[0]
- gdsdir = topdir + '/gds'
- hasgdsdir = True if os.path.isdir(gdsdir) else False
-
- ofile = open(magpath + '/generate_fill.tcl', 'w')
+ if os.path.isdir(layoutpath + '/gds'):
+ gdspath = layoutpath + '/gds'
+ elif os.path.isdir(layoutpath + '/../gds'):
+ gdspath = layoutpath + '/../gds'
+ else:
+ gdspath = layoutpath
+
+ project_file = os.path.split(user_project_path)[1]
+ project = project_file.split(os.extsep, 1)[0]
+
+ ofile = open(layoutpath + '/generate_fill.tcl', 'w')
print('#!/usr/bin/env wish', file=ofile)
print('drc off', file=ofile)
+ print('crashbackups stop', file=ofile)
print('tech unlock *', file=ofile)
print('snap internal', file=ofile)
print('box values 0 0 0 0', file=ofile)
@@ -153,12 +214,18 @@
print('set starttime [orig_clock format [orig_clock seconds] -format "%D %T"]', file=ofile)
print('puts stdout "Started: $starttime"', file=ofile)
print('', file=ofile)
+ if is_gds:
+ print('gds read ' + project_file, file=ofile)
+ # NOTE: No guarantee that the filename matches the top level cell name;
+ # might want to query using "cellname list top"
print('load ' + project + ' -dereference', 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)
+ # Use FIXED_BBOX as the boundary if it exists
+ print('catch {box values {*}[property FIXED_BBOX]}', 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)
@@ -234,7 +301,7 @@
print('quit -noprompt', file=ofile)
ofile.close()
- with open(magpath + '/generate_fill_dist.tcl', 'w') as ofile:
+ with open(layoutpath + '/generate_fill_dist.tcl', 'w') as ofile:
print('#!/usr/bin/env wish', file=ofile)
print('drc off', file=ofile)
print('tech unlock *', file=ofile)
@@ -246,7 +313,7 @@
print('gds write [file root $filename].gds', file=ofile)
print('quit -noprompt', file=ofile)
- ofile = open(magpath + '/generate_fill_final.tcl', 'w')
+ ofile = open(layoutpath + '/generate_fill_final.tcl', 'w')
print('#!/usr/bin/env wish', file=ofile)
print('drc off', file=ofile)
print('tech unlock *', file=ofile)
@@ -272,7 +339,7 @@
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 + '_fill_pattern_${x}_$y -quiet', file=ofile)
+ print(' load ' + project + '_fill_pattern_${x}_$y -silent', 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)
@@ -282,7 +349,7 @@
print('}', file=ofile)
# Now tile everything back together
- print('load ' + project + '_fill_pattern -quiet', file=ofile)
+ print('load ' + project + '_fill_pattern -silent', 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)
@@ -295,10 +362,8 @@
print('cif *hier write disable', file=ofile)
print('cif *array write disable', file=ofile)
- if hasgdsdir:
- print('gds write ../gds/' + project + '_fill_pattern.gds', file=ofile)
- else:
- print('gds write ' + project + '_fill_pattern.gds', file=ofile)
+ print('gds compress 9', file=ofile)
+ print('gds write ' + gdspath + '/' + project + '_fill_pattern.gds.gz', 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)
@@ -309,16 +374,25 @@
if not testmode:
# Diagnostic
- # print('This script will generate file ' + project + '_fill_pattern.gds')
+ # print('This script will generate file ' + project + '_fill_pattern.gds.gz')
print('This script will generate files ' + project + '_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'],
+ magic_run_opts = [
+ 'magic',
+ '-dnull',
+ '-noconsole',
+ '-rcfile', rcfile_path,
+ 'generate_fill.tcl']
+
+ if debugmode:
+ print('Running: ' + ' '.join(magic_run_opts))
+
+ mproc = subprocess.run(magic_run_opts,
stdin = subprocess.DEVNULL,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
- cwd = magpath,
+ cwd = layoutpath,
env = myenv,
universal_newlines = True)
@@ -337,24 +411,31 @@
# If using distributed mode, then run magic on each of the generated
# layout files
pool = multiprocessing.Pool()
- magfiles = glob.glob(magpath + '/' + project + '_fill_pattern_*.mag')
+ magfiles = glob.glob(layoutpath + '/' + project + '_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)
+ makegdsfunc = functools.partial(makegds, rcfile=rcfile_path)
+ pool.map(makegdsfunc, 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'],
+ magic_run_opts = [
+ 'magic',
+ '-dnull',
+ '-noconsole',
+ '-rcfile', rcfile_path,
+ 'generate_fill_final.tcl']
+
+ mproc = subprocess.run(magic_run_opts,
stdin = subprocess.DEVNULL,
stdout = subprocess.PIPE,
stderr = subprocess.PIPE,
- cwd = magpath,
+ cwd = layoutpath,
env = myenv,
universal_newlines = True)
if mproc.stdout:
@@ -369,20 +450,20 @@
if not keepmode:
# Remove fill generation script
- os.remove(magpath + '/generate_fill.tcl')
+ os.remove(layoutpath + '/generate_fill.tcl')
# Remove all individual fill tiles, leaving only the composite GDS.
- filelist = os.listdir(magpath)
+ filelist = os.listdir(layoutpath)
for file in filelist:
- if os.path.splitext(magpath + '/' + file)[1] == '.gds':
+ if os.path.splitext(layoutpath + '/' + file)[1] == '.gds':
if file.startswith(project + '_fill_pattern_'):
- os.remove(magpath + '/' + file)
+ os.remove(layoutpath + '/' + file)
if distmode:
- os.remove(magpath + '/generate_fill_dist.tcl')
- os.remove(magpath + '/generate_fill_final.tcl')
- os.remove(magpath + '/fill_gen_info.txt')
+ os.remove(layoutpath + '/generate_fill_dist.tcl')
+ os.remove(layoutpath + '/generate_fill_final.tcl')
+ os.remove(layoutpath + '/fill_gen_info.txt')
if testmode:
- magfiles = glob.glob(magpath + '/' + project + '_fill_pattern_*.mag')
+ magfiles = glob.glob(layoutpath + '/' + project + '_fill_pattern_*.mag')
for file in magfiles:
os.remove(file)