#!/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 #---------------------------------------------------------------------- # # gen_gpio_defaults.py --- # # Manipulate the magic database and GDS to create and apply defaults # to the GPIO control blocks based on the user's specification in the # user_defines.v file. # # The GPIO defaults block contains 13 bits that set the state of the # GPIO on power-up. GPIOs 0 to 4 in the user project area are fixed # and cannot be modified (to maintain access to the housekeeping SPI # on startup). GPIOs 5 to 37 are by default set to be an input pad # controlled by the user project. The file "user_defines.v" contains # the state specified by the user for each GPIO pad, and is what is # used in verilog simulation. # # This script parses the user_defines.v file to determine the state # of each GPIO. Then it creates as many new layouts as needed to # represent all unique states, modifies the caravel.mag layout # to replace the default layouts with the new ones as needed, and # generates GDS files for each of the layouts. # # gpio_defaults_block layout map: # Positions marked (in microns) for value = 0. For value = 1, move # the via 0.69um to the left. The given position is the lower left # corner position of the via. The via itself is 0.17um x 0.17um. # The values below are for the file gpio_defaults_block_1403. # Positions marked "Y" for "Programmed One?" are already moved to # the left, and so should be move 0.69um to the right if the bit # should be zero. # # Signal Via position (um) # name X Y #------------------------------------------------------------------- # gpio_defaults[0] 5.435 4.165 # gpio_defaults[1] 6.815 3.825 # gpio_defaults[2] 8.195 4.165 # gpio_defaults[3] 9.575 3.825 # gpio_defaults[4] 10.955 3.825 # gpio_defaults[5] 12.565 3.825 # gpio_defaults[6] 14.865 3.825 # gpio_defaults[7] 17.165 3.825 # gpio_defaults[8] 19.465 3.825 # gpio_defaults[9] 21.765 3.825 # gpio_defaults[10] 24.755 3.825 # gpio_defaults[11] 27.055 3.825 # gpio_defaults[12] 23.605 4.165 #------------------------------------------------------------------- import os import sys import re def usage(): print('Usage:') print('gen_gpio_defaults.py [] [-test]') print('') print('where:') print(' is the path to the project top level directory.') print('') print(' If is not given, then it is assumed to be the cwd.') print(' The file "user_defines.v" must exist in verilog/rtl/ relative to') print(' .') return 0 if __name__ == '__main__': # Coordinate pairs in microns for the zero position on each bit via_pos = [[5.435, 4.165], [6.815, 3.825], [8.195, 4.165], [9.575, 3.825], [10.955, 3.825], [12.565, 3.825], [14.865, 3.825], [17.165, 3.825], [19.465, 3.825], [21.765, 3.825], [24.755, 3.825], [27.055, 3.825], [23.605, 4.165]] optionlist = [] arguments = [] debugmode = False testmode = 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 gen_gpio_defaults.py.") usage() sys.exit(0) if '-debug' in optionlist: debugmode = True if '-test' in optionlist: testmode = True user_project_path = None if len(arguments) == 0: user_project_path = os.getcwd() else: user_project_path = arguments[0] 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) magpath = user_project_path + '/mag' gdspath = user_project_path + '/gds' vpath = user_project_path + '/verilog' glpath = vpath + '/gl' try: caravel_path = os.environ['CARAVEL_ROOT'] except: print('Warning: CARAVEL_ROOT not set; assuming the cwd.') caravel_path = os.getcwd() # Check paths 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(glpath): print('No directory ' + glpath + ' found (path to gate-level verilog).') sys.exit(1) if not os.path.isdir(magpath): print('No directory ' + magpath + ' found (path to magic databases).') sys.exit(1) # Parse the user defines verilog file kvpairs = {} user_defines_path = vpath + '/rtl/user_defines.v' if not os.path.isfile(user_defines_path): user_defines_path = caravel_path + '/verilog/rtl/user_defines.v' if os.path.isfile(user_defines_path): with open(user_defines_path, 'r') as ifile: infolines = ifile.read().splitlines() for line in infolines: tokens = line.split() if len(tokens) >= 3: if tokens[0] == '`define': if tokens[2][0] == '`': # If definition is nested, substitute value. tokens[2] = kvpairs[tokens[2]] kvpairs['`' + tokens[1]] = tokens[2] else: print('Error: No user_defines.v file found.') sys.exit(1) # Set additional dictionary entries for the fixed-configuration # GPIOs 0 to 4. This allows the layout to have the default # gpio_defaults_block layout, and this script will change it as # needed. kvpairs["`USER_CONFIG_GPIO_0_INIT"] = "13'h1803" kvpairs["`USER_CONFIG_GPIO_1_INIT"] = "13'h1803" kvpairs["`USER_CONFIG_GPIO_2_INIT"] = "13'h0403" kvpairs["`USER_CONFIG_GPIO_3_INIT"] = "13'h0403" kvpairs["`USER_CONFIG_GPIO_4_INIT"] = "13'h0403" # Generate zero and one coordinates for each via llx_zero = [] lly_zero = [] urx_zero = [] ury_zero = [] llx_one = [] lly_one = [] urx_one = [] ury_one = [] zero_string = [] one_string = [] for i in range(0, 13): llx_zero = int(via_pos[i][0] * 200) lly_zero = int(via_pos[i][1] * 200) urx_zero = llx_zero + 34 ury_zero = lly_zero + 34 llx_one = llx_zero - 138 lly_one = lly_zero urx_one = urx_zero - 138 ury_one = ury_zero zero_string.append('rect {:d} {:d} {:d} {:d}'.format(llx_zero, lly_zero, urx_zero, ury_zero)) one_string.append('rect {:d} {:d} {:d} {:d}'.format(llx_one, lly_one, urx_one, ury_one)) # Create new cells for each unique type print('Step 1: Create new cells for new GPIO default vectors.') cellsused = [None] * 38 for i in range(5, 38): config_name = '`USER_CONFIG_GPIO_' + str(i) + '_INIT' try: config_value = kvpairs[config_name] except: print('No configuration specified for GPIO ' + str(i) + '; skipping.') continue try: default_str = config_value[-4:] binval = '{:013b}'.format(int(default_str, 16)) except: print('Error: Default value ' + config_value + ' is not a 4-digit hex number; skipping') continue cell_name = 'gpio_defaults_block_' + default_str mag_file = magpath + '/' + cell_name + '.mag' cellsused[i] = cell_name # Record which bits need to be set for this binval bitflips = [] for j in range(0, 13): if binval[12 - j] == '1': bitflips.append(j) if not os.path.isfile(mag_file): # A cell with this set of defaults doesn't exist, so make it # First read the 0000 cell, then write to mag_path while # changing the position of vias on the "1" bits with open(caravel_path + '/mag/gpio_defaults_block.mag', 'r') as ifile: maglines = ifile.read().splitlines() outlines = [] for magline in maglines: is_flipped = False for bitflip in bitflips: if magline == zero_string[bitflip]: is_flipped = True break if is_flipped: outlines.append(one_string[bitflip]) else: outlines.append(magline) print('Creating new layout file ' + mag_file) if testmode: print('(Test only)') else: with open(mag_file, 'w') as ofile: for outline in outlines: print(outline, file=ofile) else: print('Layout file ' + mag_file + ' already exists and does not need to be generated.') gl_file = glpath + '/' + cell_name + '.v' defrex = re.compile('[ \t]*assign[ \t]+gpio_defaults\[([0-9]+)\]') if not os.path.isfile(gl_file): # A cell with this set of defaults doesn't exist, so make it # First read the default cell, then write to gl_path while # changing the assignment statements at the bottom of each file. with open(caravel_path + '/verilog/gl/gpio_defaults_block.v', 'r') as ifile: vlines = ifile.read().splitlines() outlines = [] for vline in vlines: is_flipped = False dmatch = defrex.match(vline) if dmatch: bitidx = int(dmatch.group(1)) if bitidx in bitflips: is_flipped = True if is_flipped: outlines.append(re.sub('_low', '_high', vline)) elif 'gpio_defaults_block' in vline: outlines.append(re.sub('gpio_defaults_block', cell_name, vline)) else: outlines.append(vline) print('Creating new gate-level verilog file ' + gl_file) if testmode: print('(Test only)') else: with open(gl_file, 'w') as ofile: for outline in outlines: print(outline, file=ofile) else: print('Gate-level verilog file ' + gl_file + ' already exists and does not need to be generated.') print('Step 2: Modify top-level layouts to use the specified defaults.') # Create a backup of the caravan and caravel layouts # if not testmode: # shutil.copy(magpath + '/caravel.mag', magpath + '/caravel.mag.bak') # shutil.copy(magpath + '/caravan.mag', magpath + '/caravan.mag.bak') if testmode: print('Test only: Caravel layout:') with open(caravel_path + '/mag/caravel.mag', 'r') as ifile: maglines = ifile.read().splitlines() outlines = [] for magline in maglines: if magline.startswith('use '): tokens = magline.split() instname = tokens[2] if instname.startswith('gpio_defaults_block_'): gpioidx = instname[20:] cellname = cellsused[int(gpioidx)] if cellname: tokens[1] = cellname outlines.append(' '.join(tokens)) if testmode: print('Replacing line: ' + magline) print('With: ' + ' '.join(tokens)) else: outlines.append(magline) else: outlines.append(magline) if not testmode: with open(magpath + '/caravel.mag', 'w') as ofile: for outline in outlines: print(outline, file=ofile) # Do the same to the top gate-level verilog instrex = re.compile('[ \t]*(gpio_defaults_block_?[0-9]*)[ \t]+gpio_defaults_block_([0-9]+)') if testmode: print('Test only: Caravel top gate-level verilog:') with open(caravel_path + '/verilog/gl/caravel.v', 'r') as ifile: vlines = ifile.read().splitlines() outlines = [] for vline in vlines: imatch = instrex.match(vline) if imatch: gpioname = imatch.group(1) gpioidx = int(imatch.group(2)) cellname = cellsused[int(gpioidx)] if cellname: outlines.append(re.sub(gpioname, cellname, vline, 1)) if testmode: print('Replacing line: ' + vline) print('With: ' + outlines[-1]) else: outlines.append(vline) else: outlines.append(vline) if not testmode: with open(glpath + '/caravel.v', 'w') as ofile: for outline in outlines: print(outline, file=ofile) if testmode: print('Test only: Caravan layout:') with open(caravel_path + '/mag/caravan.mag', 'r') as ifile: maglines = ifile.read().splitlines() outlines = [] for magline in maglines: if magline.startswith('use '): tokens = magline.split() instname = tokens[2] if instname.startswith('gpio_defaults_block_'): gpioidx = instname[20:] cellname = cellsused[int(gpioidx)] if cellname: tokens[1] = cellname outlines.append(' '.join(tokens)) if testmode: print('Replacing line: ' + magline) print('With: ' + ' '.join(tokens)) else: outlines.append(magline) else: outlines.append(magline) if not testmode: with open(magpath + '/caravan.mag', 'w') as ofile: for outline in outlines: print(outline, file=ofile) # Do the same to the top gate-level verilog if testmode: print('Test only: Caravan top gate-level verilog:') with open(caravel_path + '/verilog/gl/caravan.v', 'r') as ifile: vlines = ifile.read().splitlines() outlines = [] for vline in vlines: imatch = instrex.match(vline) if imatch: gpioname = imatch.group(1) gpioidx = int(imatch.group(2)) cellname = cellsused[int(gpioidx)] if cellname: outlines.append(re.sub(gpioname, cellname, vline, 1)) if testmode: print('Replacing line: ' + vline) print('With: ' + outlines[-1]) else: outlines.append(vline) else: outlines.append(vline) if not testmode: with open(glpath + '/caravan.v', 'w') as ofile: for outline in outlines: print(outline, file=ofile) print('Done.') sys.exit(0)