Added a new script utililty called "change_gds_cell" that can be
used to swap one cell for another in a GDS file. It is very
simple and does not consider any subcells that might be inside
the swapped cell; it is designed for quick ECOs for metal fixes.
diff --git a/common/change_gds_cell.py b/common/change_gds_cell.py
new file mode 100755
index 0000000..7e2c22a
--- /dev/null
+++ b/common/change_gds_cell.py
@@ -0,0 +1,218 @@
+#!/usr/bin/env python3
+#
+# Script to read a GDS file, and replace a single cell structure with a cell of
+# the same name from a different GDS file. If a checksum is provided, then
+# the cell contents will be checked against the checksum before allowing the
+# replacement. The checksum is just the sum of the length of all GDS records in
+# the cell. A checksum can be determined by running this routine once without
+# supplying a checksum; the checksum will be calculated and printed.
+#
+# There are no checks to ensure that the replacement cell is in any way compatible
+# with the existing cell. Validation must be done independently. This script is
+# only a simple GDS data compositor.
+
+import os
+import sys
+
+def usage():
+ print('change_gds_cell.py <cell_name> <path_to_cell_gds> <path_to_gds_in> [<path_to_gds_out>] [-checksum=<checksum>]')
+
+if __name__ == '__main__':
+ debug = False
+
+ if len(sys.argv) == 1:
+ print("No options given to change_gds_cell.py.")
+ usage()
+ sys.exit(0)
+
+ optionlist = []
+ arguments = []
+
+ for option in sys.argv[1:]:
+ if option.find('-', 0) == 0:
+ optionlist.append(option)
+ else:
+ arguments.append(option)
+
+ if len(arguments) < 3 or len(arguments) > 4:
+ print("Wrong number of arguments given to change_gds_cell.py.")
+ usage()
+ sys.exit(0)
+
+ checksum = '0'
+ for option in optionlist:
+ if option == '-debug':
+ debug = True
+ elif option.split('=')[0] == '-checksum':
+ checksum = option.split('=')[1]
+
+ try:
+ checksum = int(checksum)
+ except:
+ print('Checksum must evaluate to an integer.')
+ sys.exit(1)
+
+ cellname = arguments[0]
+ cellsource = arguments[1]
+ source = arguments[2]
+
+ # If only three arguments are provided, then overwrite the source file.
+ if len(arguments) == 4:
+ dest = arguments[3]
+ else:
+ dest = arguments[2]
+
+ cellsrcdir = os.path.split(cellsource)[0]
+ cellinfile = os.path.split(cellsource)[1]
+ sourcedir = os.path.split(source)[0]
+ gdsinfile = os.path.split(source)[1]
+
+ destdir = os.path.split(dest)[0]
+ gdsoutfile = os.path.split(dest)[1]
+
+ print('Reading GDS file for alternate cell ' + cellname)
+ with open(cellsource, 'rb') as ifile:
+ celldata = ifile.read()
+
+ #----------------------------------------------------------------------
+ # Assume that celldata contains the cell in question.
+ # Find the extend of the data from 'beginstr' to 'endstr'
+ #----------------------------------------------------------------------
+
+ datalen = len(celldata)
+ dataptr = 0
+ incell = False
+ datastart = dataend = -1
+ while dataptr < datalen:
+ # Read stream records up to 'beginstr', then save data through 'endstr'
+ bheader = celldata[dataptr:dataptr + 2]
+ reclen = int.from_bytes(bheader, 'big')
+ if reclen == 0:
+ print('Error: found zero-length record at position ' + str(dataptr))
+ break
+
+ rectype = celldata[dataptr + 2]
+ datatype = celldata[dataptr + 3]
+
+ brectype = rectype.to_bytes(1, byteorder='big')
+ bdatatype = datatype.to_bytes(1, byteorder='big')
+
+ if rectype == 5: # beginstr
+ saveptr = dataptr
+
+ elif rectype == 6: # strname
+ if datatype != 6:
+ print('Error: Structure name record is not a string!')
+ sys.exit(1)
+
+ bstring = celldata[dataptr + 4: dataptr + reclen]
+ strname = bstring.decode('ascii')
+ if strname == cellname:
+ print('Cell ' + cellname + ' found at position ' + str(saveptr))
+ datastart = saveptr
+ incell = True
+ elif debug:
+ print('Cell ' + strname + ' position ' + str(dataptr) + ' (ignored)')
+
+ elif rectype == 7: # endstr
+ if incell:
+ incell = False
+ dataend = dataptr + reclen
+ print('Cell ' + cellname + ' ends at position ' + str(dataend))
+
+ # Advance the pointer past the data
+ dataptr += reclen
+
+ if datastart == -1 or dataend == -1:
+ print('Failed to find the cell data for ' + cellname)
+ sys.exit(1)
+
+ #-----------------------------------------------------------------
+ # Now do the same thing for the source GDS file.
+ #-----------------------------------------------------------------
+
+ print('Reading GDS file for original source ' + source)
+ with open(source, 'rb') as ifile:
+ gdsdata = ifile.read()
+
+ datalen = len(gdsdata)
+ dataptr = 0
+ incell = False
+ cellchecksum = 0
+ oldstart = oldend = -1
+ while dataptr < datalen:
+ # Read stream records up to any structure, then check for structure name
+ bheader = gdsdata[dataptr:dataptr + 2]
+ reclen = int.from_bytes(bheader, 'big')
+ if reclen == 0:
+ print('Error: found zero-length record at position ' + str(dataptr))
+ break
+
+ rectype = gdsdata[dataptr + 2]
+ datatype = gdsdata[dataptr + 3]
+
+ brectype = rectype.to_bytes(1, byteorder='big')
+ bdatatype = datatype.to_bytes(1, byteorder='big')
+
+ if rectype == 5: # beginstr
+ saveptr = dataptr
+
+ elif rectype == 6: # strname
+ if datatype != 6:
+ print('Error: Structure name record is not a string!')
+ sys.exit(1)
+
+ bstring = gdsdata[dataptr + 4: dataptr + reclen]
+ strname = bstring.decode('ascii')
+ if strname == cellname:
+ print('Cell ' + cellname + ' found at position ' + str(saveptr))
+ oldstart = saveptr
+ incell = True
+ elif debug:
+ print('Cell ' + strname + ' position ' + str(dataptr) + ' (copied)')
+
+ elif rectype == 7: # endstr
+ if incell:
+ incell = False
+ cellchecksum = cellchecksum + reclen
+ oldend = dataptr + reclen
+ print('Cell ' + cellname + ' ends at position ' + str(oldend))
+ print('Cell ' + cellname + ' checksum is ' + str(cellchecksum))
+
+ # Find checksum (sum of length of all records in the cell of interest)
+ if incell:
+ cellchecksum = cellchecksum + reclen
+
+ # Advance the pointer past the data
+ dataptr += reclen
+
+ if oldstart == -1 or oldend == -1:
+ print('Failed to find the cell data for ' + cellname)
+ sys.exit(1)
+
+ if checksum != 0:
+ if cellchecksum == checksum:
+ print('Info: Structure ' + cellname + ' matches checksum ' + str(checksum))
+ else:
+ print('Info: Structure ' + cellname + ' at ' + str(oldstart) + ' to ' +
+ str(oldend) + ' has checksum ' + str(cellchecksum) +
+ ' != ' + str(checksum) + ' (checksum failure)')
+ sys.exit(1)
+ else:
+ print('Info: Structure ' + cellname + ' checksum is ' + str(cellchecksum))
+
+ print('Info: Structure ' + cellname + ' at ' + str(oldstart) + ' to ' +
+ str(oldend) + ' will be replaced by alternate data.')
+
+ before = gdsdata[0:oldstart]
+ after = gdsdata[oldend:]
+
+ cellstrdata = celldata[datastart:dataend]
+
+ # Reassemble the GDS data around the new cell
+ gdsdata = before + cellstrdata + after
+
+ with open(dest, 'wb') as ofile:
+ ofile.write(gdsdata)
+
+ exit(0)