Corrected the sky130.tech file for the required tap surrounding an LICON contact (which is zero on two sides); the rule for "diff" surround should be interpreted as meaning "diff" and not "tap".
diff --git a/sky130/custom/scripts/run_drc.py b/sky130/custom/scripts/run_drc.py index d1c3761..0513066 100755 --- a/sky130/custom/scripts/run_drc.py +++ b/sky130/custom/scripts/run_drc.py
@@ -1,42 +1,18 @@ #!/bin/env python3 #------------------------------------------------------------------------- -# s8_gen_sealring.py --- a seal ring generator for SkyWater s8 using magic. -# -# Because the seal ring contains many layers that do not appear in standard -# layout editing, they are all specially implemented in the s8seal_ring.tech -# file in this directory. -# -# An example seal ring was generated by SkyWater and imported using magic's -# gdsquery.sh script. It was then hand-edited to contain only the bottom -# quarter. Then it was saved in .mag databases. -# -# The generator script s8_gen_sealring.py calls magic using the s8seal_ring.tech -# file, and automatically modifies the geometry to stretch to the half width -# and height of the specified dimensions. Then the lower-left cells are -# copied and folded over the centerline to make the complete seal ring. -# The seal ring is then written out in GDS format. Then a simplified magic -# view is generated in the usual user-facing s8 technology file, with the -# GDS_FILE property pointing to the seal ring GDS. This layout and GDS can -# then be imported into a layout. +# run_drc.py --- A script to run magic in batch mode and apply full DRC +# checks on a layout. This inclues full DRC, antenna rule checks, and +# density checks. # # Usage: # -# s8_gen_sealring.py width height target_dir [-force] [-outer] [-keep] -# -# Where: -# width = the full-chip layout width -# height = the full-chip layout height -# target_dir = location of the full-chip layout -# -# -force = overwrite any existing files at the target -# -outer = width and height are the seal ring outer edge, not the chip area -# -keep = keep local working directory of results +# run_drc.py <layout_name> # # Results: -# Files advSeal_6um_gen.mag and advSeal_6um_gen.gds are generated and placed in -# target_dir. advSeal_6um_gen.mag is an "abstract" view that represents the -# seal ring in diffusion and the nikon cross in metal1, and references -# the advSeal_6um_gen.gds file in the same directory as a GDS_FILE property. +# +# generates a file "<layout_name>_drc.txt" containing a human-readable +# list of the DRC errors. +# #------------------------------------------------------------------------- import subprocess @@ -45,223 +21,15 @@ import os import re -def generate_sealring(width, height, target_dir, force, keep): +# Work in progress - # All files of interest are listed below. - - script = 'generate_gds.tcl' - tech = 's8seal_ring.tech' - corner = 'seal_ring_corner.mag' - abstract = 'seal_ring_corner_abstract.mag' - slots = 'sealring_slots.mag' - array = 'seal_ring_slots_array.mag' - nikon = 'nikon_sealring_shape.mag' - polygons = ['sr_polygon00007.mag', - 'sr_polygon00027.mag', 'sr_polygon00011.mag', 'sr_polygon00028.mag', - 'sr_polygon00001.mag', 'sr_polygon00015.mag', 'sr_polygon00031.mag', - 'sr_polygon00002.mag', 'sr_polygon00016.mag', 'sr_polygon00032.mag', - 'sr_polygon00003.mag', 'sr_polygon00019.mag', 'sr_polygon00035.mag', - 'sr_polygon00004.mag', 'sr_polygon00020.mag', 'sr_polygon00036.mag', - 'sr_polygon00005.mag', 'sr_polygon00023.mag', 'sr_polygon00039.mag', - 'sr_polygon00006.mag', 'sr_polygon00024.mag'] - - - # Create temporary directory - if os.path.exists('temp'): - print('temp/ directory exists. Please remove it before running.') - sys.exit(0) - - os.makedirs('temp') - os.chdir('temp') - - # Copy all .mag files, .magicrc file, and s8seal_ring.tech file to temp/ - files_to_copy = polygons[:] - files_to_copy.append(nikon) - files_to_copy.append(slots) - files_to_copy.append(array) - files_to_copy.append(corner) - files_to_copy.append(abstract) - files_to_copy.append(tech) - files_to_copy.append(script) - - for file in files_to_copy: - shutil.copy('../' + file, '.') - - # Seal ring is placed 6um outside of the chip, so add 12um to width and height - fwidth = float(width) + 12 - fheight = float(height) + 12 - - dbhwidth = int(fwidth * 100) - dbhheight = int(fheight * 100) - - swidth = str(dbhwidth) - sheight = str(dbhheight) - - swidthx5 = str(dbhwidth * 5) - - dwidth = str(int(fwidth * 200)) - dheight = str(int(fheight * 200)) - - # Modify every polygon to half width and height - - for file in polygons: - with open(file, 'r') as ifile: - maglines = ifile.read().splitlines() - - with open(file, 'w') as ofile: - for line in maglines: - newline = re.sub('51200', swidth, line) - newline = re.sub('51210', sheight, newline) - # NOTE: polygon 39 is at scale 10, not 2, due to - # corner positions of 45 degree angled geometry. - newline = re.sub('256000', swidthx5, newline) - print(newline, file=ofile) - - # Abstract corner view gets the same treatment - - qwidth = str(int(fwidth * 50)) - qheight = str(int(fheight * 50)) - - with open(abstract, 'r') as ifile: - maglines = ifile.read().splitlines() - - with open(abstract, 'w') as ofile: - for line in maglines: - newline = re.sub('25600', qwidth, line) - newline = re.sub('25605', qheight, newline) - print(newline, file=ofile) - - # Slots arrays are recalculated to span the width and height - - with open(array, 'r') as ifile: - maglines = ifile.read().splitlines() - - slotsX = False - with open(array, 'w') as ofile: - for line in maglines: - newline = line - if 'slots_X' in line: - slotsX = True - elif 'array 0' in line: - if slotsX: - nslots = int((fwidth - 25.0) / 25.0) - 1 - newline = 'array 0 ' + str(nslots) + ' 5000 0 0 430' - else: - nslots = int((fheight - 25.0) / 25.0) - 1 - newline = 'array 0 ' + str(nslots) + ' 5000 0 0 430' - - print(newline, file=ofile) - - # Corner cell changes bounding boxes to half width and height. - - with open(corner, 'r') as ifile: - maglines = ifile.read().splitlines() - - slotsX = False - with open(corner, 'w') as ofile: - for line in maglines: - newline = re.sub('51200', swidth, line) - newline = re.sub('51210', sheight, newline) - print(newline, file=ofile) - - # Create a new top-level layout called 'advSeal_6um_gen.mag' - # Mirrors uses in X and Y, and adds slots arrays at lower left - # and upper right - - with open('advSeal_6um_gen.mag', 'w') as ofile: - print('magic', file=ofile) - print('tech s8seal_ring', file=ofile) - print('magscale 1 2', file=ofile) - print('timestamp 1584630000', file=ofile) - - # Lower left original - print('use seal_ring_corner seal_ring_corner_0', file=ofile) - print('timestamp 1584562315', file=ofile) - print('transform 1 0 0 0 1 0', file=ofile) - print('box -30480 -30480 ' + swidth + ' ' + sheight, file=ofile) - - # Mirrored in X - print('use seal_ring_corner seal_ring_corner_3', file=ofile) - print('timestamp 1584562315', file=ofile) - print('transform -1 0 ' + dwidth + ' 0 1 0', file=ofile) - print('box -30480 -30480 ' + swidth + ' ' + sheight, file=ofile) - - # Mirrored in Y - print('use seal_ring_corner seal_ring_corner_1', file=ofile) - print('timestamp 1584562315', file=ofile) - print('transform 1 0 0 0 -1 ' + dheight, file=ofile) - print('box -30480 -30480 ' + swidth + ' ' + sheight, file=ofile) - - # Mirrored in both X and Y - print('use seal_ring_corner seal_ring_corner_2', file=ofile) - print('timestamp 1584562315', file=ofile) - print('transform -1 0 ' + dwidth + ' 0 -1 ' + dheight, file=ofile) - print('box -30480 -30480 ' + swidth + ' ' + sheight, file=ofile) - - # Lower left slot arrays (bottom and left sides slots) - print('use seal_ring_slots_array seal_ring_slots_array_0', file=ofile) - print('timestamp 1584629764', file=ofile) - print('transform 1 0 0 0 1 0', file=ofile) - print('box 285 285 ' + swidth + ' ' + sheight, file=ofile) - - # Upper right slot arrays (top and right sides slots) - print('use seal_ring_slots_array seal_ring_slots_array_1', file=ofile) - print('timestamp 1584629764', file=ofile) - print('transform -1 0 ' + dwidth + ' 0 -1 ' + dheight, file=ofile) - print('box 285 285 ' + swidth + ' ' + sheight, file=ofile) - - print('<< end >>', file=ofile) - - # Create a new abstract layout TO BE called 'advSeal_6um_gen.mag' - # This is the view in technology EFS8A. Since there is already - # a cell with this name that is used to generate GDS, the cell - # will be called "seal_ring.mag" and copied to "advSeal_6um_gen.mag" - # in the target directory. - - xwidth = str(dbhwidth) - xheight = str(dbhheight) - - with open('seal_ring.mag', 'w') as ofile: - print('magic', file=ofile) - print('tech EFS8A', file=ofile) - print('timestamp 1584566829', file=ofile) - - # Lower left original - print('use seal_ring_corner_abstract seal_ring_corner_abstract_0', file=ofile) - print('timestamp 1584566221', file=ofile) - print('transform 1 0 0 0 1 0', file=ofile) - print('box 0 0 ' + qwidth + ' ' + qheight, file=ofile) - - # Mirrored in X - print('use seal_ring_corner_abstract seal_ring_corner_abstract_3', file=ofile) - print('timestamp 1584566221', file=ofile) - print('transform -1 0 ' + xwidth + ' 0 1 0', file=ofile) - print('box 0 0 ' + qwidth + ' ' + qheight, file=ofile) - - # Mirrored in Y - print('use seal_ring_corner_abstract seal_ring_corner_abstract_1', file=ofile) - print('timestamp 1584566221', file=ofile) - print('transform 1 0 0 0 -1 ' + xheight, file=ofile) - print('box 0 0 ' + qwidth + ' ' + qheight, file=ofile) - - # Mirrored in both X and Y - print('use seal_ring_corner_abstract seal_ring_corner_abstract_2', file=ofile) - print('timestamp 1584566221', file=ofile) - print('transform -1 0 ' + xwidth + ' 0 -1 ' + xheight, file=ofile) - print('box 0 0 ' + qwidth + ' ' + qheight, file=ofile) - - print('<< properties >>', file=ofile) - print('string LEFview no_prefix', file=ofile) - print('string GDS_FILE advSeal_6um_gen.gds', file=ofile) - print('string GDS_START 0', file=ofile) - print('string FIXED_BBOX 0 0 ' + swidth + ' ' + sheight, file=ofile) - - print('<< end >>', file=ofile) +def run_full_drc(layout_name, output_file): + with open('run_magic_drc.tcl', 'w') as ofile: # Create the GDS of the seal ring mproc = subprocess.run(['magic', '-dnull', '-noconsole', - 'generate_gds.tcl'], + 'run_magic_drc.tcl'], stdin = subprocess.DEVNULL, stdout = subprocess.PIPE, stderr = subprocess.PIPE, universal_newlines = True) if mproc.stdout: @@ -274,40 +42,7 @@ if mproc.returncode != 0: print('ERROR: Magic exited with status ' + str(mproc.returncode)) - # Copy the GDS file and the abstract view to the target directory - - os.chdir('..') - - if not os.path.exists(target_dir): - os.makedirs(target_dir) - - print('Installing files to ' + target_dir) - if force or not os.path.exists(target_dir + '/advSeal_6um_gen.gds'): - shutil.copy('temp/advSeal_6um_gen.gds', target_dir) - else: - print('ERROR: advSeal_6um_gen.gds already exists at target! Use -force to overwrite.') - if force or not os.path.exists(target_dir + '/advSeal_6um_gen.mag'): - shutil.copy('temp/seal_ring.mag', target_dir + '/advSeal_6um_gen.mag') - else: - print('ERROR: advSeal_6um_gen.mag already exists at target! Use -force to overwrite.') - if force or not os.path.exists(target_dir + '/seal_ring_corner_abstract.mag'): - shutil.copy('temp/seal_ring_corner_abstract.mag', target_dir) - else: - print('ERROR: seal_ring_corner_abstract.mag already exists at target! Use -force to overwrite.') - - # Remove the temporary directory and its contents - - if not keep: - shutil.rmtree('temp') - else: - print('Retaining generated files in temp/ directory') - - # Done! - print('Done generating files advSeal_6um_gen.gds and advSeal_6um_gen.mag in ' + target_dir) - print('Place the seal ring cell in the final layout at (0um, 0um) before generating GDS.') - print('The top level layout minus seal ring must have a lower left corner of (6um, 6um)') - -# If called as main, run generate_sealring() +# If called as main, run all DRC tests if __name__ == '__main__': @@ -320,30 +55,22 @@ else: arguments.append(item) - force = True if '-force' in options else False - keep = True if '-keep' in options else False - outer = True if '-outer' in options else False - - # Need one argument: path to verilog netlist + # Need one argument: path to layout # If two arguments, then 2nd argument is the output file. - if len(arguments) == 3: - width = arguments[0] - height = arguments[1] - target_dir = arguments[2] + if len(arguments) < 3: + layout_root = arguments[1] - # Seal ring is 12um thick, so if "outer" option is used, subtract 12um - # from both width and height. - if outer: - width = str(float(width) - 12.0) - height = str(float(height) - 12.0) - - generate_sealring(width, height, target_dir, force, keep) + if len(arguments == 1): + out_fileroot = layout_root + "_drc.txt" else: - print("Usage: s8_gen_sealring.py <width> <height> <target_dir> [options]") + out_fileroot = arguments[2] + + if len(arguments) < 3: + run_full_drc(layout_root, out_filename) + else: + print("Usage: run_drc.py <layout_name> [<output_file>] [options]") print("Options:") - print(" -outer : Width and height are seal ring outer edge, not chip area") - print(" -force : Overwrite any existing files at <target_dir>") - print(" -keep : Keep generated files in temp/ directory") + print(" (none)")
diff --git a/sky130/magic/sky130.tech b/sky130/magic/sky130.tech index 498b73a..e9d0b99 100644 --- a/sky130/magic/sky130.tech +++ b/sky130/magic/sky130.tech
@@ -5224,11 +5224,6 @@ surround pdic/a *pdi 40 absence_illegal \ "P-diode overlap of N-diode contact < %d (licon.5a)" - surround nsc/a *nsd 40 absence_illegal \ - "N-tap overlap of N-tap contact < %d (licon.5a)" - surround psc/a *psd 40 absence_illegal \ - "P-tap overlap of P-tap contact < %d (licon.5a)" - spacing psc/a allnactivenontap 60 touching_illegal \ "Min. space between P-tap contact and butting N diffusion < %d (licon.5b)" spacing nsc/a allpactivenontap 60 touching_illegal \ @@ -5257,11 +5252,6 @@ surround mvpdic/a *mvpdi 40 absence_illegal \ "P-diode overlap of N-diode contact < %d (licon.5a)" - surround mvnsc/a *mvnsd 40 absence_illegal \ - "N-tap overlap of N-tap contact < %d (licon.5a)" - surround mvpsc/a *mvpsd 40 absence_illegal \ - "P-tap overlap of P-tap contact < %d (licon.5a)" - spacing mvpsc/a allndiffmvnontap 60 touching_illegal \ "Min. space between P-tap contact and butting N diffusion < %d (licon.5b)" spacing mvnsc/a allpdiffmvnontap 60 touching_illegal \