Files
tt-support-tools/tt_db_config.py
T
Pat Deegan 89ad9bfa93 feat(fpga): add support for the new FabricFox devkit
* TT FPGA tool, prelim import
* config.ini manager, prelim
* bitstream uploads and FPGA config.ini management

Contributed by @psychogenic, with minor edits by @htfab
2026-02-12 06:27:29 +01:00

240 lines
6.2 KiB
Python
Executable File

#!/usr/bin/env python3
"""
TT config.ini manager
# global defaults
python tt_db_config.py --project tt_um_factory_test --mode ASIC_MANUAL_INPUTS
# project specific
python tt_db_config.py --name tt_um_factory_test project --clockrate 100
"""
import argparse
import logging
import os
import subprocess
import sys
import tempfile
from configupdater import ConfigUpdater
DefaultPort = "/dev/ttyACM0"
CommandProject = "project"
CommandDefault = "default"
def getParser():
parser = argparse.ArgumentParser(
description="TT Demoboard Configuration Tool",
# formatter_class=argparse.RawDescriptionHelpFormatter
)
# Global options (available to all subcommands)
parser.add_argument(
"--port",
help=f"port for connection to DB [{DefaultPort}]",
required=False,
default=DefaultPort,
)
parser.add_argument("--name", help="project/section name", required=False)
parser.add_argument(
"--debug",
metavar="LEVEL",
help="debug logging",
action="store_const",
dest="loglevel",
const=logging.DEBUG,
default=logging.INFO,
)
# Create subparsers
subparsers = parser.add_subparsers(
dest="command",
required=True, # make subcommand mandatory
help="Available commands",
)
defaults = subparsers.add_parser(CommandDefault, help="Default settings")
defaults.add_argument(
"--project",
metavar="PROJNAME",
type=str,
help="Set the default project on boot",
)
defaults.add_argument(
"--mode",
metavar="I/O MODE",
type=str,
choices=["SAFE", "ASIC_RP_CONTROL", "ASIC_MANUAL_INPUTS"],
)
project = subparsers.add_parser(CommandProject, help="Project overrides")
project.add_argument(
"--clockrate",
metavar="RATE",
type=int,
help="Clock rate in Hz",
)
project.add_argument(
"--set-default",
action="store_true",
help="set this project as the default on startup",
)
return parser
class ConfigFile:
def __init__(self, filepath: str):
self.filepath = filepath
self.updater = ConfigUpdater()
self.updater.read(filepath)
return
def save(self):
with open(self.filepath, "w") as f:
self.updater.write(f)
def set_default_project(self, name: str):
self.updater["DEFAULT"]["project"] = name
def set_default_mode(self, mode: str):
self.updater["DEFAULT"]["mode"] = mode
def set_project_clockrate(self, name: str, rate: int):
if name not in self.updater:
self.updater.add_section(name)
self.updater[name]["clock_frequency"] = str(rate)
class TTDBConfig:
def __init__(self, port: str = DefaultPort):
self.port = port
@classmethod
def get_temp_file(cls, prefix="ttconf-", suffix=".tmp", dir=None, mode="wb"):
# path is tmp.name
tmp = tempfile.NamedTemporaryFile(
mode=mode, # text mode + read/write
delete=False, # ← keep file after closing
suffix=suffix, # optional but recommended
prefix=prefix,
dir=dir, # None = system temp dir
)
return tmp
@classmethod
def get_temp_filename(cls, prefix="ttconf-", suffix=".tmp", dir=None):
tmp = cls.get_temp_file(prefix, suffix, dir)
tmp.close()
return tmp.name
def reset(self):
commands = ["connect", self.port, "+", "reset"]
subprocess.run(["mpremote", *commands], check=False)
def deploy(self, files: list, destination_dir: str, do_reset: bool = True):
commands = ["connect", self.port]
if isinstance(files, str):
files = [files]
for f in files:
bname = os.path.basename(f)
commands += ["+", "cp", f, f":{destination_dir}/{bname}"]
if do_reset:
commands += ["+", "reset"]
subprocess.run(["mpremote", *commands], check=True)
def write_file(
self, local_filepath: str, remote_filepath: str, do_reset: bool = False
):
commands = [
"connect",
self.port,
"+",
"cp",
local_filepath,
f":{remote_filepath}",
]
if do_reset:
commands += ["+", "reset"]
subprocess.run(["mpremote", *commands], check=False)
def fetch_safe(self, remote_filepath: str):
tmp_filepath = self.get_temp_filename()
commands = ["connect", self.port]
commands += ["+", "cp", f":{remote_filepath}", tmp_filepath]
subprocess.run(["mpremote", *commands], check=True)
return tmp_filepath
def fetch(self, files: list, destination_dir: str = "."):
commands = ["connect", self.port]
if isinstance(files, str):
files = [files]
for f in files:
bname = os.path.basename(f)
commands += ["+", "cp", f":{f}", f"{destination_dir}/{bname}"]
subprocess.run(["mpremote", *commands], check=True)
def get_config_ini(self) -> ConfigFile:
tmpfile = self.fetch_safe("/config.ini")
return ConfigFile(tmpfile)
def upload_config_ini(self, local_filepath: str, do_reset: bool = True):
self.write_file(local_filepath, "/config.ini", do_reset)
if __name__ == "__main__":
parser = getParser()
args = parser.parse_args()
ttconf = TTDBConfig()
cnf = None
if args.command == CommandDefault:
cnf = ttconf.get_config_ini()
if args.project:
cnf.set_default_project(args.project)
if args.mode:
cnf.set_default_mode(args.mode)
elif args.command == CommandProject:
if not args.name:
parser.print_help()
print("MUST specify NAME of project this applies to using --name")
sys.exit(1)
cnf = ttconf.get_config_ini()
if args.clockrate:
cnf.set_project_clockrate(args.name, args.clockrate)
if args.set_default:
cnf.set_default_project(args.name)
if cnf is not None:
cnf.save()
ttconf.upload_config_ini(cnf.filepath, do_reset=False)
ttconf.reset()