Added a full fill generation script which breaks a design up into
tiles for faster, more memory-efficient processing.  This includes
some changes to the techfile to create two variants for the fill
patterning, one that should be used for a full-chip pattern, and
one to be used with the script.  Also removed the patch file from
yesterday's commit.
diff --git a/sky130/custom/scripts/generate_fill.py b/sky130/custom/scripts/generate_fill.py
new file mode 100755
index 0000000..9a1e3dc
--- /dev/null
+++ b/sky130/custom/scripts/generate_fill.py
@@ -0,0 +1,239 @@
+#!/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 subprocess
+
+def usage():
+    print("Usage:")
+    print("generate_fill.py <layout_name> [-keep] [-test]")
+    print("")
+    print("where:")
+    print("    <layout_name> is the path to the .mag 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.")
+    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.py.")
+        usage()
+        sys.exit(1)
+
+    user_project_path = arguments[0]
+
+    magpath = 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!')
+            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
+
+    rcfile = magpath + '/.magicrc'
+    if not os.path.isfile(rcfile):
+        rcfile = None
+
+    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
+
+    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)
+        print('load ' + project + ' -dereference', 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 / $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 / $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('        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 + '_fill_pattern_${x}_$y', file=ofile)
+        print('        load ' + project + '_fill_pattern_${x}_$y', file=ofile)
+
+        # Remove any GDS_FILE reference
+        print('        property GDS_FILE ""', file=ofile)
+        # Set boundary using comment layer, to the size of the step box
+        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 + '_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 + '_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')
+        print('gds rescale false')
+        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 + '_fill_pattern_${x}_$y', 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 + '_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 + '_fill_pattern', 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 + '_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 + '_fill_pattern.gds', file=ofile)
+        else:
+            print('gds write ' + project + '_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 + '_fill_pattern.gds')
+        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'],
+		stdin = subprocess.DEVNULL,
+		stdout = subprocess.PIPE,
+		stderr = subprocess.PIPE,
+		cwd = magpath,
+		env = myenv,
+		universal_newlines = True)
+        if mproc.stdout:
+            for line in mproc.stdout.splitlines():
+                print(line)
+        if mproc.stderr:
+            print('Error message output from magic:')
+            for line in mproc.stderr.splitlines():
+                print(line)
+            if mproc.returncode != 0:
+                print('ERROR:  Magic exited with status ' + str(mproc.returncode))
+
+    if not keepmode:
+        # Remove fill generation script
+        os.remove(magpath + '/generate_fill.tcl')
+        # Remove all individual fill tiles, leaving only the composite GDS.
+        filelist = os.listdir(magpath)
+        for file in filelist:
+            if os.path.splitext(magpath + '/' + file)[1] == '.gds':
+                if file.startswith(project + '_fill_pattern_')
+                    os.remove(magpath + '/' + file
+
+    print('Done!')
+    exit(0)
diff --git a/sky130/magic/sky130.tech b/sky130/magic/sky130.tech
index 95fc2f4..67047b2 100644
--- a/sky130/magic/sky130.tech
+++ b/sky130/magic/sky130.tech
@@ -1707,7 +1707,7 @@
 #endif (EXPERIMENTAL)
 
 #----------------------------------------------------------------
-style wafflefill
+style wafflefill variants (),(tiled)
 #----------------------------------------------------------------
 # Style used by scripts for automatically generating fill layers
 # NOTE: Be sure to generate output on flattened layout.
@@ -1717,10 +1717,30 @@
  gridlimit 5
 
 #----------------------------------------------------------------
-# Generate and retain a layer representing the bounding box
+# Generate and retain a layer representing the bounding box.
+#
+# For variant ():
+# The bounding box is the full extent of geometry on the top level
+# cell.
+#
+# For variant (tiled):
+# Use with a script that breaks layout into flattened tiles and runs
+# fill individually on each.  The tiles should be larger than the
+# step size, and each should draw a layer "comment" the size of the
+# step box.
 #----------------------------------------------------------------
- templayer	topbox
-	bbox	top
+
+ variants ()
+     templayer	topbox
+	 bbox	top
+
+ variants (tiled)
+     templayer	topbox comment
+	 # Each tile imposes the full keepout distance rule of
+	 # 3um on all sides.
+	 shrink 1500
+
+ variants *
 
 #----------------------------------------------------------------
 # Generate guard-band around nwells to keep FOM from crossing
@@ -1732,25 +1752,27 @@
  templayer mvnwell
  	bloat-all alldiffmv nwell
 
- templayer lvnwell nwell
+ templayer lvnwell allnwell
 	and-not mvnwell
 
  templayer	well_shrink mvnwell
 	shrink 	250
 	or lvnwell
 	shrink	180
- templayer	well_guardband nwell
+ templayer	well_guardband allnwell
 	grow	340
 	and-not	well_shrink
 
 #---------------------------------------------------
 # Diffusion and poly keep-out areas
 #---------------------------------------------------
- templayer      obstruct_fom alldiff,allpoly,rpw
+ templayer      obstruct_fom alldiff,allpoly,fomfill,polyfill,obspoly,obsactive
+	or	rpw,pnp,npn
         grow    500
 	or	well_guardband
 
- templayer      obstruct_poly alldiff,allpoly,rpw
+ templayer      obstruct_poly alldiff,allpolyfomfill,polyfill,obspoly,obsactive
+	or	rpw,pnp,npn
 	grow	1000
 
 #---------------------------------------------------
@@ -2045,6 +2067,7 @@
  templayer	met4fill_medium topbox
         slots   0 1000 300 0 1000 300 700 1050
         and-not obstruct_m4_medium
+	and	topbox
         shrink  495
         grow    495