From 89ad9bfa93f524b4c94ca8b9398d093e4680ed0c Mon Sep 17 00:00:00 2001 From: Pat Deegan Date: Thu, 12 Feb 2026 00:27:29 -0500 Subject: [PATCH] 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 --- fpga/tt_fpga_fabricfoxv2.pcf | 37 ++++ project.py | 58 +----- project_info.py | 17 +- pyproject.toml | 4 +- requirements.txt | 9 +- tech.py | 31 ++- tt_db_config.py | 239 +++++++++++++++++++++++ tt_fpga.py | 354 +++++++++++++++++++++++++++++++++++ tt_logging.py | 20 ++ tt_tool.py | 39 +--- 10 files changed, 705 insertions(+), 103 deletions(-) create mode 100644 fpga/tt_fpga_fabricfoxv2.pcf create mode 100755 tt_db_config.py create mode 100755 tt_fpga.py create mode 100644 tt_logging.py diff --git a/fpga/tt_fpga_fabricfoxv2.pcf b/fpga/tt_fpga_fabricfoxv2.pcf new file mode 100644 index 0000000..63a5631 --- /dev/null +++ b/fpga/tt_fpga_fabricfoxv2.pcf @@ -0,0 +1,37 @@ +# FabricFox PCF +set_io -nowarn clk 20 +set_io -nowarn intclk 35 +set_io -nowarn rst_n 37 + +set_io -nowarn rgb[0] 39 +set_io -nowarn rgb[1] 40 +set_io -nowarn rgb[2] 41 + +set_io -nowarn ui_in[0] 13 +set_io -nowarn ui_in[1] 19 +set_io -nowarn ui_in[2] 18 +set_io -nowarn ui_in[3] 21 +set_io -nowarn ui_in[4] 23 +set_io -nowarn ui_in[5] 25 +set_io -nowarn ui_in[6] 26 +set_io -nowarn ui_in[7] 27 + +set_io -nowarn uio[0] 2 +set_io -nowarn uio[1] 4 +set_io -nowarn uio[2] 3 +set_io -nowarn uio[3] 6 +set_io -nowarn uio[4] 9 +set_io -nowarn uio[5] 10 +set_io -nowarn uio[6] 11 +set_io -nowarn uio[7] 12 + +set_io -nowarn uo_out[0] 38 +set_io -nowarn uo_out[1] 42 +set_io -nowarn uo_out[2] 43 +set_io -nowarn uo_out[3] 44 +set_io -nowarn uo_out[4] 45 +set_io -nowarn uo_out[5] 46 +set_io -nowarn uo_out[6] 47 +set_io -nowarn uo_out[7] 48 + +set_frequency clk 24.0 diff --git a/project.py b/project.py index 5482009..9308ca7 100644 --- a/project.py +++ b/project.py @@ -353,6 +353,8 @@ class Project: return os.path.join(self.local_dir, f"{self.info.top_module}.v") def get_tile_sizes(self): + if self.tech.is_fpga: + return {} tile_sizes_yaml = f"tech/{self.pdk}/tile_sizes.yaml" with open( os.path.join(os.path.dirname(__file__), tile_sizes_yaml), "r" @@ -629,62 +631,6 @@ class Project: logging.error("librelane failed") exit(1) - def create_fpga_bitstream(self, args): - logging.info(f"Creating FPGA bitstream for {self}") - - target_map = { - "classic": { - "pcf": "tt_fpga_top.pcf", - "def_name": "tt_fpga", - }, - "fabricfox": {"pcf": "tt_fpga_fabricfox.pcf", "def_name": "tt_ff_fpga"}, - } - pcf_file = target_map[args.fpga_breakout_target]["pcf"] - - base_name = args.fpga_bitstream_name - if not len(base_name): - base_name = target_map[args.fpga_breakout_target]["def_name"] - - with open(os.path.join(SCRIPT_DIR, "fpga/tt_fpga_top.v"), "r") as f: - top_module_template = f.read() - - with open(os.path.join(self.src_dir, "_tt_fpga_top.v"), "w") as f: - f.write( - top_module_template.replace("__tt_um_placeholder", self.info.top_module) - ) - - build_dir = os.path.join(self.local_dir, "build") - os.makedirs(build_dir, exist_ok=True) - - sources = [os.path.join(self.src_dir, src) for src in self.sources] - source_list = " ".join(sources) - - yosys_cmd = f"yosys -l {build_dir}/01-synth.log -DSYNTH -p 'synth_ice40 -top tt_fpga_top -json {build_dir}/{base_name}.json' src/_tt_fpga_top.v {source_list}" - logging.debug(yosys_cmd) - p = subprocess.run(yosys_cmd, shell=True) - if p.returncode != 0: - logging.error("synthesis failed") - exit(1) - - seed = os.getenv("TT_FPGA_SEED", "10") - freq = os.getenv("TT_FPGA_FREQ", "12") - - nextpnr_cmd = f"nextpnr-ice40 -l {build_dir}/02-nextpnr.log --pcf-allow-unconstrained --seed {seed} --freq {freq} --package sg48 --up5k --asc {build_dir}/{base_name}.asc --pcf {SCRIPT_DIR}/fpga/{pcf_file} --json {build_dir}/{base_name}.json" - logging.debug(nextpnr_cmd) - p = subprocess.run(nextpnr_cmd, shell=True) - if p.returncode != 0: - logging.error("placement failed") - exit(1) - - icepack_cmd = f"icepack {build_dir}/{base_name}.asc {build_dir}/{base_name}.bin" - logging.debug(icepack_cmd) - p = subprocess.run(icepack_cmd, shell=True) - if p.returncode != 0: - logging.error("bitstream creation failed failed") - exit(1) - - logging.info(f"Bitstream created successfully: {build_dir}/{base_name}.bin") - def create_project_datasheet(self, template_version: str): template_args = copy.deepcopy(self.info.__dict__) template_args.update( diff --git a/project_info.py b/project_info.py index ee49b9a..c886a04 100644 --- a/project_info.py +++ b/project_info.py @@ -106,13 +106,16 @@ class ProjectInfo: else: self.description: str = description - tiles = project_section.get("tiles") - if tiles is None: - errors.append("Missing key 'tiles' in 'project' section") - elif tiles not in tile_sizes.keys(): - errors.append(f"Invalid value for 'tiles' in 'project' section: {tiles}") - else: - self.tiles: str = tiles + if tile_sizes: + tiles = project_section.get("tiles") + if tiles is None: + errors.append("Missing key 'tiles' in 'project' section") + elif tiles not in tile_sizes.keys(): + errors.append( + f"Invalid value for 'tiles' in 'project' section: {tiles}" + ) + else: + self.tiles: str = tiles analog_pins = project_section.get("analog_pins", 0) if not isinstance(analog_pins, int): diff --git a/pyproject.toml b/pyproject.toml index 68a6245..a902b76 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,9 @@ dependencies = [ "PyYAML", "requests", "yowasp-yosys", - "matplotlib" + "matplotlib", + "configupdater", + "mpremote" ] [tool.setuptools] diff --git a/requirements.txt b/requirements.txt index e77d2ab..da5db0d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile with Python 3.12 +# This file is autogenerated by pip-compile with Python 3.13 # by the following command: # # pip-compile @@ -20,6 +20,8 @@ chevron==0.14.0 # via tt-support-tools (pyproject.toml) click==8.2.1 # via yowasp-yosys +configupdater==3.2 + # via tt-support-tools (pyproject.toml) contourpy==1.3.3 # via matplotlib cssselect2==0.8.0 @@ -56,6 +58,8 @@ matplotlib==3.10.6 # via tt-support-tools (pyproject.toml) mistune==3.1.3 # via tt-support-tools (pyproject.toml) +mpremote==1.27.0 + # via tt-support-tools (pyproject.toml) nodeenv==1.9.1 # via pre-commit numpy==1.26.4 @@ -74,6 +78,7 @@ pillow==11.3.0 # matplotlib platformdirs==4.3.8 # via + # mpremote # virtualenv # yowasp-runtime pluggy==1.6.0 @@ -86,6 +91,8 @@ pygments==2.19.2 # via pytest pyparsing==3.2.5 # via matplotlib +pyserial==3.5 + # via mpremote pytest==8.4.2 # via tt-support-tools (pyproject.toml) python-dateutil==2.9.0.post0 diff --git a/tech.py b/tech.py index 219d0d4..25dace3 100644 --- a/tech.py +++ b/tech.py @@ -2,7 +2,7 @@ import json import os from typing import Dict, List, Literal, Pattern, Protocol, Tuple, TypedDict, Union -TechName = Literal["sky130A", "ihp-sg13g2", "gf180mcuD"] +TechName = Literal["sky130A", "ihp-sg13g2", "gf180mcuD", "fpgaUp5k"] class CellDefinition(TypedDict): @@ -16,6 +16,7 @@ class PDKVersionInfo(TypedDict): class Tech(Protocol): + is_fpga: bool def_suffix: str librelane_pdk_args: str tt_corner: str @@ -65,6 +66,7 @@ def parse_openpdks_pdk_version( class Sky130Tech(Tech): + is_fpga = False def_suffix = "pg" librelane_pdk_args = "" tt_corner = "nom_tt_025C_1v80" @@ -124,6 +126,7 @@ class Sky130Tech(Tech): class IHPTech(Tech): + is_fpga = False def_suffix = "pgvdd" librelane_pdk_args = "--pdk ihp-sg13g2" tt_corner = "nom_typ_1p20V_25C" @@ -177,6 +180,7 @@ class IHPTech(Tech): class GF180MCUDTech(Tech): + is_fpga = False def_suffix = "pgvdd" librelane_pdk_args = "--pdk gf180mcuD" tt_corner = "nom_tt_025C_3v30" @@ -248,8 +252,33 @@ class GF180MCUDTech(Tech): return cells +class FpgaTech(Tech): + is_fpga = True + # dummy values to keep the type checker happy + def_suffix = "" + librelane_pdk_args = "" + tt_corner = "" + cell_regexp = r"" + netlist_type = "nl" + project_top_metal_layer = "" + librelane_config = {} + label_layers = [] + buried_layers = [] + scramble_cells = None + mux_config_yaml_name = "" + mux_macros = [] + extra_logo_macros = [] + prboundary_layer = (0, 0) + logo_layer = (0, 0) + logo_layer_name = "" + logo_pixel_size = 0 + read_pdk_version = Tech.read_pdk_version + load_cell_definitions = Tech.load_cell_definitions + + tech_map: dict[TechName, Tech] = { "ihp-sg13g2": IHPTech(), "gf180mcuD": GF180MCUDTech(), "sky130A": Sky130Tech(), + "fpgaUp5k": FpgaTech(), } diff --git a/tt_db_config.py b/tt_db_config.py new file mode 100755 index 0000000..68716b5 --- /dev/null +++ b/tt_db_config.py @@ -0,0 +1,239 @@ +#!/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() diff --git a/tt_fpga.py b/tt_fpga.py new file mode 100755 index 0000000..ac648e1 --- /dev/null +++ b/tt_fpga.py @@ -0,0 +1,354 @@ +#!/usr/bin/env python3 +""" + tt_fpga -- FPGA breakout management tool + @author: Pat Deegan + + In a project based on a TT template, with a configured info.yaml, all you need + is to run + + python tt/tt_fpga.py harden + + to generate a bitstream in build/. Then + + python tt/tt_fpga.py configure --upload + + will make the bitstream available through the tt.shuttle object in the DB REPL. + + You may also set this as the default project to load on startup and/or set the + auto-clocking rate through the configure command, e.g. + + python tt/tt_fpga.py configure --upload --set-default --clockrate 1000 + + to do all three at once. + + + If you have no info.yaml template, the script will work anyway. But you'll need + to specify more command line arguments. + + See + python tt/tt_fpga.py harden --help + and + python tt/tt_fpga.py configure --help + +""" + + +import argparse +import logging +import os +import subprocess +import sys + +from project import SCRIPT_DIR, Project +from tt_db_config import DefaultPort, TTDBConfig +from tt_logging import setup_logging + +CommandHarden = "harden" +CommandConfig = "configure" + + +def getParser(): + parser = argparse.ArgumentParser( + description="TT FPGA tool", + # formatter_class=argparse.RawDescriptionHelpFormatter + ) + + parser.add_argument("--project-dir", help="location of the project", default=".") + # parser.add_argument("--yaml", help="the project's yaml configuration file", default="info.yaml") + + # Global options (available to all subcommands) + 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", + ) + + # ---------------------- + # Subcommand: hardening + # ---------------------- + harden = subparsers.add_parser(CommandHarden, help="Run hardening process") + + harden.add_argument( + "--name", + metavar="FILE", + help="Output file and project name (default: top_module name)", + default="", + ) + + harden.add_argument( + "--breakout-target", + help="Select target breakout: classic (TT04), fabricfox (TT ETR db) default: fabricfox", + choices=["classic", "fabricfox"], + default="fabricfox", + ) + + harden.add_argument( + "--source_dir", + metavar="DIR", + help="Directory containing source verilog files (default from info.yaml)", + ) + + harden.add_argument( + "--source", + action="append", + metavar="FILE", + help="Source file(s) to harden (specify multiple times for multiple source files, default from info.yaml)", + ) + + harden.add_argument( + "--top_module", + metavar="TOP", + help="Name of the top module (default from info.yaml)", + ) + + # ---------------------- + # Subcommand: configuration + # ---------------------- + config = subparsers.add_parser( + CommandConfig, help="Upload bitstream and manage configuration" + ) + config.add_argument( + "--port", + metavar="COMPORT", + type=str, + default=DefaultPort, + help=f"Port for uploads (default: {DefaultPort})", + ) + + config.add_argument( + "--set-default", + action="store_true", + help="set this project as the default on startup", + ) + config.add_argument( + "--upload", action="store_true", help="upload the bitstream.bin from build/" + ) + config.add_argument( + "--clockrate", + metavar="RATE", + type=int, + help="Clock rate in Hz", + ) + + config.add_argument( + "--name", + metavar="FILE", + help="Output file and project name (default: top_module name)", + default="", + ) + + return parser + + +def die_with_error(msg): + print(msg) + sys.exit(2) + + +class TTFPGA: + + def __init__(self, proj: Project | None, args): + self.project = proj + self.args = args + + def get_name(self): + base_name = self.args.name + if not len(base_name): + if ( + "top_module" in self.args + and self.args.top_module is not None + and len(self.args.top_module) + ): + return self.args.top_module + if self.project is not None: + base_name = self.project.info.top_module + + if not len(base_name): # shouldn't happen + base_name = "tt_fpga_project" + + return base_name + + @property + def bitstream_filename(self): + return f"{self.get_name()}.bin" + + @property + def top_module(self): + if "top_module" in self.args and self.args.top_module: + # command line overrides yaml + return self.args.top_module + + if self.project is not None: + return self.project.info.top_module + + if "top_module" in self.args: + if not self.args.top_module: + die_with_error("No project yaml, must specify top_module") + else: + return None + return self.args.top_module + + @property + def source_dir(self): + + if self.args.source_dir: + # command line overrides yaml + return self.args.source_dir + + if self.project is not None: + return self.project.src_dir + if not self.args.source_dir: + die_with_error("No project yaml, must specify source_dir") + return self.args.source_dir + + @property + def local_dir(self): + + if self.project is not None: + return self.project.local_dir + + return os.path.realpath(self.args.project_dir) + + @property + def sources(self): + + if self.args.source and len(self.args.source): + # command line overrides yaml + return self.args.source + + if self.project is not None: + return self.project.sources + + if not self.args.source: + die_with_error( + "No project yaml, must specify --source A.v --source B.v ..." + ) + + # print(self.args.source) + return self.args.source + + def create_fpga_bitstream(self): + logging.info(f"Creating FPGA bitstream for {self.project}") + + target_map = { + "classic": { + "pcf": "tt_fpga_top.pcf", + }, + "fabricfox": { + "pcf": "tt_fpga_fabricfoxv2.pcf", + }, + } + args = self.args + + pcf_file = target_map[args.breakout_target]["pcf"] + + base_name = self.get_name() + + with open(os.path.join(SCRIPT_DIR, "fpga/tt_fpga_top.v"), "r") as f: + top_module_template = f.read() + + with open(os.path.join(self.source_dir, "_tt_fpga_top.v"), "w") as f: + f.write(top_module_template.replace("__tt_um_placeholder", self.top_module)) + + build_dir = os.path.join(self.local_dir, "build") + os.makedirs(build_dir, exist_ok=True) + + sources = [os.path.join(self.source_dir, src) for src in self.sources] + source_list = " ".join(sources) + + yosys_cmd = f"yosys -l {build_dir}/01-synth.log -DSYNTH -p 'read_verilog -sv src/_tt_fpga_top.v {source_list}; synth_ice40 -top tt_fpga_top -json {build_dir}/{base_name}.json'" + logging.debug(yosys_cmd) + p = subprocess.run(yosys_cmd, shell=True) + if p.returncode != 0: + logging.error("synthesis failed") + exit(1) + + seed = os.getenv("TT_FPGA_SEED", "10") + freq = os.getenv("TT_FPGA_FREQ", "12") + + nextpnr_cmd = f"nextpnr-ice40 -l {build_dir}/02-nextpnr.log --pcf-allow-unconstrained --seed {seed} --freq {freq} --package sg48 --up5k --asc {build_dir}/{base_name}.asc --pcf {SCRIPT_DIR}/fpga/{pcf_file} --json {build_dir}/{base_name}.json" + logging.debug(nextpnr_cmd) + p = subprocess.run(nextpnr_cmd, shell=True) + if p.returncode != 0: + logging.error("placement failed") + exit(1) + + icepack_cmd = f"icepack {build_dir}/{base_name}.asc {build_dir}/{base_name}.bin" + logging.debug(icepack_cmd) + p = subprocess.run(icepack_cmd, shell=True) + if p.returncode != 0: + logging.error("bitstream creation failed failed") + exit(1) + + logging.info(f"Bitstream created successfully: {build_dir}/{base_name}.bin") + + +def yaml_file_exists(args): + return os.path.exists(os.path.join(os.path.realpath(args.project_dir), "info.yaml")) + + +def main(): + + parser = getParser() + + args = parser.parse_args() + + setup_logging(args.loglevel) + + if args.command != CommandHarden and args.command != CommandConfig: + parser.print_help() + sys.exit(1) + + project = None + if yaml_file_exists(args): + project = Project( + 0, "unknown", args.project_dir, pdk="fpgaUp5k", is_user_project=True + ) + project.post_clone_setup() + + fpga = TTFPGA(project, args) + + if args.command == CommandHarden: + fpga.create_fpga_bitstream() + elif args.command == CommandConfig: + proj_name = fpga.get_name() + bitstream_name = f"{proj_name}.bin" + fname = f"build/{bitstream_name}" + if not os.path.exists(fname): + print(f"Can't find {fname}") + sys.exit(3) + ttdbconf = TTDBConfig(args.port) + if args.upload: + print(f"Uploading {fname}") + ttdbconf.write_file(fname, f"/bitstreams/{bitstream_name}") + if args.set_default or args.clockrate is not None: + cnf = ttdbconf.get_config_ini() + if args.set_default: + cnf.set_default_project(proj_name) + if args.clockrate is not None: + cnf.set_project_clockrate(proj_name, args.clockrate) + + cnf.save() # save local file + # upload that config.ini + ttdbconf.upload_config_ini(cnf.filepath, do_reset=False) + ttdbconf.reset() # restart + + if not (args.upload or args.set_default or args.clockrate): + print( + "Did nothing. Use configure with --upload, --set-default and/or --clockrate" + ) + + +if __name__ == "__main__": + main() diff --git a/tt_logging.py b/tt_logging.py new file mode 100644 index 0000000..b435326 --- /dev/null +++ b/tt_logging.py @@ -0,0 +1,20 @@ +import logging +import sys + + +def setup_logging(log_level): + + # 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(log_level) + + # create console handler and set level to info + ch = logging.StreamHandler(sys.stdout) + # create formatter for console + ch.setFormatter(log_format) + log.addHandler(ch) diff --git a/tt_tool.py b/tt_tool.py index ab1bbe9..bd2f77e 100755 --- a/tt_tool.py +++ b/tt_tool.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 import argparse import logging -import sys from project import Project from project_checks import check_project_docs from tech import TechName +from tt_logging import setup_logging if __name__ == "__main__": parser = argparse.ArgumentParser(description="TT setup") @@ -143,41 +143,9 @@ if __name__ == "__main__": const=True, ) - # FPGA - parser.add_argument( - "--create-fpga-bitstream", - help="create the FPGA bitstream", - action="store_const", - const=True, - ) - parser.add_argument( - "--fpga-breakout-target", - help="Select target breakout: classic (TT04) or fabricfox (TT06+)", - choices=["classic", "fabricfox"], - default="classic", - ) - parser.add_argument( - "--fpga-bitstream-name", - help="Set the bitstream name", - default="", - ) - 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) + setup_logging(args.loglevel) pdk: TechName = "ihp-sg13g2" if args.ihp else "gf180mcuD" if args.gf else "sky130A" @@ -228,6 +196,3 @@ if __name__ == "__main__": if args.create_svg: project.create_svg() - - if args.create_fpga_bitstream: - project.create_fpga_bitstream(args)