caravel/scripts/gen_gpio_defaults.py

534 lines
20 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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 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.92um to the right. 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] 7.505 17.425
# gpio_defaults[1] 11.645 17.425
# gpio_defaults[2] 4.745 6.545
# gpio_defaults[3] 7.965 6.545
# gpio_defaults[4] 11.645 6.545
# gpio_defaults[5] 5.205 9.605
# gpio_defaults[6] 5.205 11.985
# gpio_defaults[7] 8.425 15.045
# gpio_defaults[8] 7.965 20.485
# gpio_defaults[9] 4.745 20.485
# gpio_defaults[10] 5.205 15.045
# gpio_defaults[11] 12.105 9.605
# gpio_defaults[12] 8.885 9.605
#-------------------------------------------------------------------
import os
import sys
import re
import glob
import subprocess
import gzip
def usage():
print('Usage:')
print('gen_gpio_defaults.py [<path_to_project>] [-test]')
print('')
print('where:')
print(' <path_to_project> is the path to the project top level directory.')
print('')
print(' If <path_to_project> 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(' <path_to_project>.')
return 0
def smart_open(filename, mode="rt"):
"""
Opens a file normally if uncompressed, or with gzip if its a .gz file.
Args:
filename (str): Path to the file.
mode (str): File mode ('rt' for text, 'rb' for binary, etc.).
Returns:
File object (either normal open or gzip open).
"""
if filename.endswith(".gz"):
return gzip.open(filename, mode)
else:
return open(filename, mode)
if __name__ == '__main__':
# Coordinate pairs in microns for the zero position on each bit
via_pos = [[7.505, 17.425], [11.645, 17.425], [4.745, 6.545], [7.965, 6.545],
[11.645, 6.545], [5.205, 9.605], [5.205, 11.985], [8.425, 15.045],
[7.965, 20.485], [4.745, 20.485], [5.205, 15.045], [12.105, 9.605],
[8.885, 9.605]]
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'
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(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:
raw_data = ifile.read()
comment_pattern = r'//.*|/\*[\s\S]*?\*/'
data_without_comments = re.sub(comment_pattern, '', raw_data)
for line in data_without_comments.splitlines():
tokens = line.split()
if len(tokens) >= 3:
if tokens[0] == '`define':
if tokens[2][0] == '`':
# If definition is nested, substitute value.
try:
tokens[2] = kvpairs[tokens[2]]
except:
print('Error: Used unknown definition ' + 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'h0801"
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 = round(via_pos[i][0] * 200)
lly_zero = round(via_pos[i][1] * 200)
urx_zero = llx_zero + 34
ury_zero = lly_zero + 34
llx_one = llx_zero + 184
lly_one = lly_zero
urx_one = urx_zero + 184
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
# Remove pre-existing versions of mag and verilog files because they may be out-of-date.
for old_mag_file in glob.glob(magpath + '/gpio_defaults_block_*.mag'):
os.remove(old_mag_file)
for old_verilog_file in glob.glob(glpath + '/gpio_defaults_block_*.v'):
os.remove(old_verilog_file)
for i in range(0, 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
valid_value = r"^13'h[0-9a-fA-F]{4}$"
value_match = re.match(valid_value, config_value)
if not value_match:
print('Error: Default value ' + config_value + ' is not a 4-digit hex number; skipping')
continue
try:
default_str = config_value[-4:]
binval = '{:013b}'.format(int(default_str, 16))
if len(binval) > 13:
print('Error: Default value ' + config_value + ' is not a 4-digit hex number; skipping')
continue
except:
print('Error: Default value ' + config_value + ' is not a 4-digit hex number; skipping')
continue
cell_name = 'gpio_defaults_block_' + default_str.lower()
mag_file = magpath + '/' + cell_name + '.mag'
cellsused[i] = cell_name
# Record which bits need to be set for this binval
bitflips = []
notflipped = []
for j in range(0, 13):
if binval[12 - j] == '1':
bitflips.append(j)
else:
notflipped.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
reverse_flipped = False
for bitflip in bitflips:
if magline == zero_string[bitflip]:
is_flipped = True
break
if not is_flipped:
for bitflip in notflipped:
if magline == one_string[bitflip]:
reverse_flipped = True
break
if is_flipped:
outlines.append(one_string[bitflip])
elif reverse_flipped:
outlines.append(zero_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
is_reversed = False
dmatch = defrex.match(vline)
if dmatch:
bitidx = int(dmatch.group(1))
if bitidx in bitflips:
is_flipped = True
else:
is_reversed = True
if is_flipped:
outlines.append(re.sub('_low', '_high', vline))
elif is_reversed:
outlines.append(re.sub('_high', '_low', 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')
idx1rex = re.compile('gpio_defaults_block_([0-9]+)..([0-9]+)')
idx2rex = re.compile('gpio_defaults_block_([0-9]+)')
if testmode:
print('Test only: Caravel core layout:')
# Check for compressed layout
if os.path.isfile(caravel_path + '/mag/caravel_core.mag'):
caravel_core_magfile = caravel_path + '/mag/caravel_core.mag'
elif os.path.isfile(caravel_path + '/mag/caravel_core.mag.gz'):
caravel_core_magfile = caravel_path + '/mag/caravel_core.mag.gz'
else:
print(f' Could not find {caravel_path}/mag/caravel_core.mag[.gz]')
sys.exit(2)
with smart_open(caravel_core_magfile, 'rt') 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_'):
imatch = idx1rex.match(instname)
if imatch:
gpioidx = int(imatch.group(1)) + int(imatch.group(2))
else:
imatch = idx2rex.match(instname)
if imatch:
gpioidx = int(imatch.group(1))
else:
print('Error: instance ' + instname + ' not a defaults block?')
cellname = cellsused[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_core.mag', 'w') as ofile:
for outline in outlines:
print(outline, file=ofile)
# Do the same to the core gate-level verilog
inst1rex = re.compile('[ \t]*(gpio_defaults_block_?[0-1]?[0-9A-Fa-f]*)[ \t]+.?gpio_defaults_block_([0-9]+).([0-9]+)')
inst2rex = re.compile('[ \t]*(gpio_defaults_block_?[0-1]?[0-9A-Fa-f]*)[ \t]+gpio_defaults_block_([0-9]+)')
if testmode:
print('Test only: Caravel top gate-level verilog:')
with open(caravel_path + '/verilog/gl/caravel_core.v', 'r') as ifile:
vlines = ifile.read().splitlines()
outlines = []
for vline in vlines:
imatch = inst1rex.match(vline)
if imatch:
gpioidx = int(imatch.group(2)) + int(imatch.group(3))
else:
imatch = inst2rex.match(vline)
if imatch:
gpioidx = int(imatch.group(2))
if imatch:
gpioname = imatch.group(1)
cellname = cellsused[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_core.v', 'w') as ofile:
for outline in outlines:
print(outline, file=ofile)
# IMPORTANT NOTE:
# This needs to be changed to caravan_core, but the cell does not yet
# exist.
if testmode:
print('Test only: Caravan layout:')
# Check for compressed layout
if os.path.isfile(caravel_path + '/mag/caravan_core.mag'):
caravan_core_magfile = caravel_path + '/mag/caravan_core.mag'
elif os.path.isfile(caravel_path + '/mag/caravan_core.mag.gz'):
caravan_core_magfile = caravel_path + '/mag/caravan_core.mag.gz'
else:
print(f' Could not find {caravel_path}/mag/caravan_core.mag[.gz]')
sys.exit(2)
with smart_open(caravan_core_magfile, 'rt') 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_'):
imatch = idx1rex.match(instname)
if imatch:
gpioidx = int(imatch.group(1)) + int(imatch.group(2))
else:
imatch = idx2rex.match(instname)
if imatch:
gpioidx = int(imatch.group(1))
else:
print('Error: instance ' + instname + ' not a defaults block?')
cellname = cellsused[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_core.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_core.v', 'r') as ifile:
vlines = ifile.read().splitlines()
outlines = []
for vline in vlines:
imatch = inst1rex.match(vline)
if imatch:
gpioidx = int(imatch.group(2)) + int(imatch.group(3))
else:
imatch = inst2rex.match(vline)
if imatch:
gpioidx = int(imatch.group(2))
if imatch:
gpioname = imatch.group(1)
cellname = cellsused[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_core.v', 'w') as ofile:
for outline in outlines:
print(outline, file=ofile)
print('Done.')
sys.exit(0)