mirror of https://github.com/efabless/caravel.git
492 lines
18 KiB
Python
Executable File
492 lines
18 KiB
Python
Executable File
#!/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 subprocess
|
|
|
|
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
|
|
|
|
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:
|
|
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.
|
|
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
|
|
|
|
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
|
|
|
|
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 = []
|
|
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
|
|
is_compressed = False
|
|
if not os.path.isfile(caravel_path + '/mag/caravel_core.mag'):
|
|
if os.path.isfile(caravel_path + '/mag/caravel_core.mag.gz'):
|
|
is_compressed = True
|
|
print('Uncompressing caravel_core.mag')
|
|
subprocess.run(['gunzip', caravel_path + '/mag/caravel_core.mag.gz'])
|
|
|
|
with open(caravel_path + '/mag/caravel_core.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_'):
|
|
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)
|
|
|
|
if is_compressed:
|
|
print('Compressing caravel_core.mag')
|
|
subprocess.run(['gzip', '-n', '--best', caravel_path +
|
|
'/mag/caravel_core.mag'])
|
|
|
|
# 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:')
|
|
with open(caravel_path + '/mag/caravan_core.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_'):
|
|
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)
|