blob: 9730dd4e6d3da9e20eff8d8ed1e0d817a49482de [file] [log] [blame]
#!/usr/bin/env python3
import requests
import argparse
import os
import glob
import json
import yaml
import logging
import sys
import csv
import re
import jinja2
GPIO_VALID_RANGE = [8, 36]
def load_yaml(yaml_file):
with open(yaml_file, "r") as stream:
return (yaml.safe_load(stream))
def write_user_config(module_name, sources, io_ranges):
env = jinja2.Environment(
loader = jinja2.FileSystemLoader('verilog/rtl')
)
top_module_template = env.get_template('tiny_user_project.v.jinja2')
with open('verilog/rtl/tiny_user_project.v', 'w') as fh:
fh.write(top_module_template.render(
module_name=module_name,
io_in_range=io_ranges[0],
io_out_range=io_ranges[1]
))
user_defines_template = env.get_template('user_defines.v.jinja2')
with open('verilog/rtl/user_defines.v', 'w') as fh:
fh.write(user_defines_template.render(
io_in_range=io_ranges[0],
io_out_range=io_ranges[1]
))
with open('openlane/tiny_user_project/config.json', 'r') as fh:
config_json = json.load(fh)
sources.append('verilog/rtl/defines.v')
sources.append('verilog/rtl/tiny_user_project.v')
config_json['VERILOG_FILES'] = [f'dir::../../{s}' for s in sources]
with open('openlane/tiny_user_project/config.json', 'w') as fh:
json.dump(config_json, fh, indent=4)
def get_project_source(yaml):
# wokwi_id must be an int or 0
try:
wokwi_id = int(yaml['project']['wokwi_id'])
except ValueError:
logging.error("wokwi id must be an integer")
exit(1)
# it's a wokwi project
if wokwi_id != 0:
url = "https://wokwi.com/api/projects/{}/verilog".format(wokwi_id)
logging.info("trying to download {}".format(url))
r = requests.get(url)
if r.status_code != 200:
logging.warning("couldn't download {}".format(url))
exit(1)
filename = "user_module.v"
with open(os.path.join('verilog/rtl', filename), 'wb') as fh:
fh.write(r.content)
# also fetch the wokwi diagram
url = "https://wokwi.com/api/projects/{}/diagram.json".format(wokwi_id)
logging.info("trying to download {}".format(url))
r = requests.get(url)
if r.status_code != 200:
logging.warning("couldn't download {}".format(url))
exit(1)
with open(os.path.join('verilog/rtl', "wokwi_diagram.json"), 'wb') as fh:
fh.write(r.content)
return [f'verilog/rtl/{filename}', 'verilog/rtl/cells.v']
# else it's HDL, so check source files
else:
if 'source_files' not in yaml['project']:
logging.error("source files must be provided if wokwi_id is set to 0")
exit(1)
source_files = yaml['project']['source_files']
if source_files is None:
logging.error("must be more than 1 source file")
exit(1)
if len(source_files) == 0:
logging.error("must be more than 1 source file")
exit(1)
if 'top_module' not in yaml['project']:
logging.error("must provide a top module name")
exit(1)
return source_files
# documentation
def check_docs(yaml):
for key in ['author', 'title', 'description', 'how_it_works', 'how_to_test', 'language']:
if key not in yaml['documentation']:
logging.error("missing key {} in documentation".format(key))
exit(1)
if yaml['documentation'][key] == "":
logging.error("missing value for {} in documentation".format(key))
exit(1)
# if provided, check discord handle is valid
if len(yaml['documentation']['discord']):
parts = yaml['documentation']['discord'].split('#')
if len(parts) != 2 or len(parts[0]) == 0 or not re.match('^[0-9]{4}$', parts[1]):
logging.error(f'Invalid format for discord username')
exit(1)
def get_top_module(yaml):
wokwi_id = int(yaml['project']['wokwi_id'])
if wokwi_id != 0:
return "user_module_{}".format(wokwi_id)
else:
return yaml['project']['top_module']
def get_io_ranges(yaml):
input_range = (GPIO_VALID_RANGE[0], GPIO_VALID_RANGE[0]+len(yaml['documentation']['inputs']))
output_range = (input_range[1], input_range[1]+len(yaml['documentation']['outputs']))
gpio_end = output_range[1]
if gpio_end > GPIO_VALID_RANGE[1]:
raise Exception('ETOOMANY IOs')
return (input_range, output_range)
def get_stats():
with open('runs/wokwi/reports/metrics.csv') as f:
report = list(csv.DictReader(f))[0]
print('# Routing stats')
print()
print('| Utilisation | Wire length (um) |')
print('|-------------|------------------|')
print('| {} | {} |'.format(report['OpenDP_Util'], report['wire_length']))
if __name__ == '__main__':
parser = argparse.ArgumentParser(description="TT setup")
parser.add_argument('--check-docs', help="check the documentation part of the yaml", action="store_const", const=True)
parser.add_argument('--get-stats', help="print some stats from the run", action="store_const", const=True)
parser.add_argument('--create-user-config', help="create the user_config.tcl file with top module and source files", action="store_const", const=True)
parser.add_argument('--debug', help="debug logging", action="store_const", dest="loglevel", const=logging.DEBUG, default=logging.INFO)
parser.add_argument('--yaml', help="yaml file to load", default='info.yaml')
args = parser.parse_args()
# setup log
log_format = logging.Formatter('%(asctime)s - %(module)-10s - %(levelname)-8s - %(message)s')
# configure the client logging
log = logging.getLogger('')
# has to be set to debug as is the root logger
log.setLevel(args.loglevel)
# create console handler and set level to info
ch = logging.StreamHandler(sys.stdout)
# create formatter for console
ch.setFormatter(log_format)
log.addHandler(ch)
if args.get_stats:
get_stats()
elif args.check_docs:
logging.info("checking docs")
config = load_yaml(args.yaml)
check_docs(config)
elif args.create_user_config:
logging.info("creating include file")
config = load_yaml(args.yaml)
source_files = get_project_source(config)
top_module = get_top_module(config)
assert top_module != 'top'
io_ranges = get_io_ranges(config)
write_user_config(top_module, source_files, io_ranges)