#!/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 #---------------------------------------------------------------------- # # set_user_id.py --- # # Manipulate the magic database, GDS, and verilog source files for the # user_id_programming block to set the user ID number. # # The user ID number is a 32-bit value that is passed to this routine # as an 8-digit hex number. If not given as an option, then the script # will look for the value of the key "project_id" in the info.yaml file # in the project top level directory # # user_id_programming layout map: # Positions marked (in microns) for value = 0. For value = 1, move # the via 0.92um to the left. # # Layout grid is 0.46um x 0.34um with half-pitch offset (0.23um, 0.17um) # # Signal Via position (um) # name X Y #-------------------------------- # mask_rev[0] 14.49 9.35 # mask_rev[1] 16.33 9.35 # mask_rev[2] 10.35 20.23 # mask_rev[3] 8.05 9.35 # mask_rev[4] 28.29 9.35 # mask_rev[5] 21.85 25.67 # mask_rev[6] 8.05 20.23 # mask_rev[7] 20.47 9.35 # mask_rev[8] 17.25 17.85 # mask_rev[9] 25.53 12.07 # mask_rev[10] 22.31 20.23 # mask_rev[11] 13.11 9.35 # mask_rev[12] 23.69 23.29 # mask_rev[13] 24.15 12.07 # mask_rev[14] 13.57 17.85 # mask_rev[15] 23.23 6.97 # mask_rev[16] 24.15 17.85 # mask_rev[17] 8.51 17.85 # mask_rev[18] 23.69 20.23 # mask_rev[19] 10.81 23.29 # mask_rev[20] 14.95 6.97 # mask_rev[21] 18.17 23.29 # mask_rev[22] 21.39 17.85 # mask_rev[23] 26.45 25.67 # mask_rev[24] 9.89 17.85 # mask_rev[25] 15.87 17.85 # mask_rev[26] 26.45 17.85 # mask_rev[27] 8.51 6.97 # mask_rev[28] 10.81 9.35 # mask_rev[29] 27.83 20.23 # mask_rev[30] 16.33 23.29 # mask_rev[31] 8.05 14.79 #---------------------------------------------------------------------- import os import sys import re import subprocess def usage(): print("Usage:") print("set_user_id.py [] []") print("") print("where:") print(" is a character string of eight hex digits, and") print(" is the path to the project top level directory.") print("") print(" If is not given, then it must exist in the info.yaml file.") print(" If is not given, then it is assumed to be the cwd.") return 0 if __name__ == '__main__': # Coordinate pairs in microns for the zero position on each bit mask_rev = ( (14.49, 9.35), (16.33, 9.35), (10.35, 20.23), ( 8.05, 9.35), (28.29, 9.35), (21.85, 25.67), ( 8.05, 20.23), (20.47, 9.35), (17.25, 17.85), (25.53, 12.07), (22.31, 20.23), (13.11, 9.35), (23.69, 23.29), (24.15, 12.07), (13.57, 17.85), (23.23, 6.97), (24.15, 17.85), ( 8.51, 17.85), (23.69, 20.23), (10.81, 23.29), (14.95, 6.97), (18.17, 23.29), (21.39, 17.85), (26.45, 25.67), ( 9.89, 17.85), (15.87, 17.85), (26.45, 17.85), ( 8.51, 6.97), (10.81, 9.35), (27.83, 20.23), (16.33, 23.29), ( 8.05, 14.79)); optionlist = [] arguments = [] debugmode = False reportmode = 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 set_user_id.py.") usage() sys.exit(0) if '-debug' in optionlist: debugmode = True if '-report' in optionlist: reportmode = True user_id_value = None user_project_path = None 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: user_project_path = arguments[0] 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 directories 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 not user_id_value: print('Error: No project_id key:value pair found in project info.yaml.') sys.exit(1) try: user_id_int = int('0x' + user_id_value, 0) user_id_bits = '{0:032b}'.format(user_id_int) except: print('Error: Cannot parse user ID "' + user_id_value + '" as an 8-digit hex number.') sys.exit(1) else: print('Error: No info.yaml file and no user ID argument given.') sys.exit(1) if reportmode: print(str(user_id_int)) sys.exit(0) print('Setting project user ID to: ' + user_id_value) magpath = user_project_path + '/mag' gdspath = user_project_path + '/gds' vpath = user_project_path + '/verilog' errors = 0 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) print('Step 1: Modify GDS of the user_id_programming subcell') # Bytes leading up to via position are: viarec = "00 06 0d 02 00 43 00 06 0e 02 00 2c 00 2c 10 03 " viabytes = bytes.fromhex(viarec) # Check for either GDS file being gzipped gdsbakgz = gdspath + '/user_id_prog_zero.gds.gz' gdsfilegz = gdspath + '/user_id_programming.gds.gz' if os.path.isfile(gdsbakgz): subprocess.run(['gunzip', gdsbakgz], stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL) zero_zipped = True else: zero_zipped = False if os.path.isfile(gdsfilegz): subprocess.run(['gunzip', gdsfilegz], stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL) file_zipped = True else: file_zipped = False # Read the GDS file. If a backup was made of the zero-value # program, then use it. gdsbak = gdspath + '/user_id_prog_zero.gds' gdsfile = gdspath + '/user_id_programming.gds' if os.path.isfile(gdsbak): with open(gdsbak, 'rb') as ifile: gdsdata = ifile.read() else: with open(gdsfile, 'rb') as ifile: gdsdata = ifile.read() for i in range(0,32): # Ignore any zero bits. if user_id_bits[i] == '0': continue coords = mask_rev[i] xum = coords[0] yum = coords[1] # Contact is 0.17 x 0.17, so add and subtract 0.085 to get # the corner positions. xllum = xum - 0.085 yllum = yum - 0.085 xurum = xum + 0.085 yurum = yum + 0.085 # Get the 4-byte hex values for the corner coordinates xllnm = round(xllum * 1000) yllnm = round(yllum * 1000) xllhex = '{0:08x}'.format(xllnm) yllhex = '{0:08x}'.format(yllnm) xurnm = round(xurum * 1000) yurnm = round(yurum * 1000) xurhex = '{0:08x}'.format(xurnm) yurhex = '{0:08x}'.format(yurnm) # Magic's GDS output for vias always starts at the lower left # corner and goes counterclockwise, repeating the first point. viaoldposdata = viarec + xllhex + yllhex + xurhex + yllhex viaoldposdata += xurhex + yurhex + xllhex + yurhex + xllhex + yllhex # For "one" bits, the X position is moved 0.92 microns to the left newxllum = xllum - 0.92 newxurum = xurum - 0.92 # Get the 4-byte hex values for the new corner coordinates newxllnm = round(newxllum * 1000) newxllhex = '{0:08x}'.format(newxllnm) newxurnm = round(newxurum * 1000) newxurhex = '{0:08x}'.format(newxurnm) vianewposdata = viarec + newxllhex + yllhex + newxurhex + yllhex vianewposdata += newxurhex + yurhex + newxllhex + yurhex + newxllhex + yllhex # Diagnostic if debugmode: print('Bit ' + str(i) + ':') print('Via position ({0:3.2f}, {1:3.2f}) to ({2:3.2f}, {3:3.2f})'.format(xllum, yllum, xurum, yurum)) print('Old hex string = ' + viaoldposdata) print('New hex string = ' + vianewposdata) # Convert hex strings to byte arrays viaoldbytedata = bytearray.fromhex(viaoldposdata) vianewbytedata = bytearray.fromhex(vianewposdata) # Replace the old data with the new if viaoldbytedata not in gdsdata: print('Error: via not found for bit position ' + str(i)) errors += 1 else: gdsdata = gdsdata.replace(viaoldbytedata, vianewbytedata) if errors == 0: # Keep a copy of the original if not os.path.isfile(gdsbak): if file_zipped: if os.path.isfile(gdsfilegz): os.rename(gdsfilegz, gdsbakgz) else: os.rename(gdsfile, gdsbak) subprocess.run(['gzip', gdsbak, '-n', '--best'], stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL) else: os.rename(gdsfile, gdsbak) with open(gdsfile, 'wb') as ofile: ofile.write(gdsdata) if file_zipped: subprocess.run(['gzip', gdsfile, '-n', '--best'], stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL) print('Done!') else: print('There were errors in processing. No file written.') print('Ending process.') sys.exit(1) print('Step 2: Add user project ID parameter to verilog.') changed = False with open(vpath + '/rtl/caravel.v', 'r') as ifile: vlines = ifile.read().splitlines() outlines = [] for line in vlines: oline = re.sub("parameter USER_PROJECT_ID = 32'h[0-9A-F]+;", "parameter USER_PROJECT_ID = 32'h" + user_id_value + ";", line) if oline != line: changed = True outlines.append(oline) if changed: with open(vpath + '/rtl/caravel.v', 'w') as ofile: for line in outlines: print(line, file=ofile) print('Done!') else: print('Error: No substitutions done on verilog/rtl/caravel.v.') print('Ending process.') sys.exit(1) print('Step 3: Add user project ID text to top level layout.') with open(magpath + '/user_id_textblock.mag', 'r') as ifile: maglines = ifile.read().splitlines() outlines = [] digit = 0 wasseen = {} for line in maglines: if 'alphaX_' in line: dchar = user_id_value[7 - digit].upper() oline = re.sub('alpha_[0-9A-F]', 'alpha_' + dchar, line) # Add path reference if cell was not previously found in the file if dchar not in wasseen: if 'hexdigits' not in oline: oline += ' hexdigits' outlines.append(oline) wasseen[dchar] = True digit += 1 else: outlines.append(line) if digit == 8: with open(magpath + '/user_id_textblock.mag', 'w') as ofile: for line in outlines: print(line, file=ofile) print('Done!') elif digit == 0: print('Error: No digits were replaced in the layout.') else: print('Error: Only ' + str(digit) + ' digits were replaced in the layout.') sys.exit(0)