diff --git a/f4pga/flows/__init__.py b/f4pga/flows/__init__.py index 5663a9dd6..af61731ef 100755 --- a/f4pga/flows/__init__.py +++ b/f4pga/flows/__init__.py @@ -42,7 +42,7 @@ from f4pga.flows.stage import Stage from f4pga.flows.common import set_verbosity_level, sfprint from f4pga.flows.argparser import setup_argparser -from f4pga.flows.commands import cmd_build, cmd_show_dependencies, f4pga_done +from f4pga.flows.commands import cmd_build, cmd_show_dependencies, cmd_run_util, f4pga_done def platform_stages(platform_flow, r_env): @@ -78,6 +78,10 @@ def main(): cmd_show_dependencies(args) f4pga_done() + if args.command == "utils": + cmd_run_util(args) + f4pga_done() + sfprint(0, "Please use a command.\nUse `--help` flag to learn more.") f4pga_done() diff --git a/f4pga/flows/argparser.py b/f4pga/flows/argparser.py index 8a159180f..24ce9d124 100644 --- a/f4pga/flows/argparser.py +++ b/f4pga/flows/argparser.py @@ -17,7 +17,7 @@ # # SPDX-License-Identifier: Apache-2.0 -from argparse import ArgumentParser, Namespace +from argparse import ArgumentParser, Namespace, REMAINDER from re import finditer as re_finditer @@ -67,6 +67,14 @@ def p_setup_show_dep_parser(parser: ArgumentParser): p_add_flow_arg(parser) +def _setup_utils_parser(parser: ArgumentParser): + parser.add_argument("--function", "-f", metavar="fun_name", type=str, help="Run specific funtion from given module") + + parser.add_argument("util", metavar="util_name", type=str, help="Name of the script to call") + + parser.add_argument("util_args", metavar="util_args", nargs=REMAINDER, type=str, help="Arguments for called script") + + def setup_argparser(): """ Set up argument parser for the program. @@ -82,6 +90,9 @@ def setup_argparser(): show_dep = subparsers.add_parser("showd", description="Show the value(s) assigned to a dependency") p_setup_show_dep_parser(show_dep) + run_util = subparsers.add_parser("utils", description="Run utility script") + _setup_utils_parser(run_util) + return parser diff --git a/f4pga/flows/commands.py b/f4pga/flows/commands.py index 468bd3768..c8087843b 100644 --- a/f4pga/flows/commands.py +++ b/f4pga/flows/commands.py @@ -29,6 +29,7 @@ from f4pga.flows.common import ( bin_dir_path, share_dir_path, + aux_dir_path, F4PGAException, ResolutionEnv, fatal, @@ -50,6 +51,7 @@ from f4pga.flows.flow import Flow from f4pga.flows.stage import Stage from f4pga.flows.inspector import get_module_info +from f4pga.util import Util ROOT = Path(__file__).resolve().parent @@ -121,7 +123,7 @@ def f4pga_done(): def setup_resolution_env(): """Sets up a ResolutionEnv with default built-ins.""" - r_env = ResolutionEnv({"shareDir": share_dir_path, "binDir": bin_dir_path}) + r_env = ResolutionEnv({"shareDir": share_dir_path, "binDir": bin_dir_path, "auxDir": aux_dir_path}) def _noisy_warnings(): """ @@ -301,3 +303,10 @@ def cmd_show_dependencies(args: Namespace): sfprint(0, prstr) set_verbosity_level(-1) + + +def cmd_run_util(args: Namespace): + """Run utility script""" + + util = Util(args.util, args.function, args.util_args) + util.exec() diff --git a/f4pga/flows/common.py b/f4pga/flows/common.py index f2efe8f7e..318e3137d 100644 --- a/f4pga/flows/common.py +++ b/f4pga/flows/common.py @@ -30,6 +30,7 @@ bin_dir_path = str(Path(sys_argv[0]).resolve().parent.parent) share_dir_path = str(F4PGA_SHARE_DIR) +aux_dir_path = str(Path(__file__).resolve().parent.parent / "aux") class F4PGAException(Exception): diff --git a/f4pga/flows/flow.py b/f4pga/flows/flow.py index 9352d6208..5ee13213f 100644 --- a/f4pga/flows/flow.py +++ b/f4pga/flows/flow.py @@ -21,7 +21,7 @@ from colorama import Fore, Style -from f4pga.flows.common import deep, sfprint, bin_dir_path, share_dir_path, F4PGAException +from f4pga.flows.common import deep, sfprint, bin_dir_path, share_dir_path, aux_dir_path, F4PGAException from f4pga.flows.cache import F4Cache from f4pga.flows.flow_config import FlowConfig from f4pga.flows.runner import ModRunCtx, module_map, module_exec @@ -103,7 +103,9 @@ def _config_mod_runctx( elif config_paths.get(prod.name): produces[prod.name] = config_paths[prod.name] - return ModRunCtx(share_dir_path, bin_dir_path, {"takes": takes, "produces": produces, "values": values}) + return ModRunCtx( + share_dir_path, bin_dir_path, aux_dir_path, {"takes": takes, "produces": produces, "values": values} + ) @staticmethod def _cache_deps(path: str, f4cache: F4Cache): diff --git a/f4pga/flows/module.py b/f4pga/flows/module.py index 0e25b7b12..47f0dfaa4 100644 --- a/f4pga/flows/module.py +++ b/f4pga/flows/module.py @@ -74,6 +74,7 @@ class ModuleContext: share: str # Absolute path to F4PGA's share directory bin: str # Absolute path to F4PGA's bin directory + aux: str # Absolute path to F4PGA's aux directory takes: SimpleNamespace # Maps symbolic dependency names to relative paths. produces: SimpleNamespace # Contains mappings for explicitely specified dependencies. # Useful mostly for checking for on-demand optional outputs (such as logs) with @@ -101,7 +102,7 @@ def _getreqmaybe(self, obj, deps: "list[str]", deps_cfg: "dict[str, ]"): setattr(obj, name, self.r_env.resolve(value)) # `config` should be a dictionary given as modules input. - def __init__(self, module: Module, config: "dict[str, ]", r_env: ResolutionEnv, share: str, bin: str): + def __init__(self, module: Module, config: "dict[str, ]", r_env: ResolutionEnv, share: str, bin: str, aux: str): self.module_name = module.name self.takes = SimpleNamespace() self.produces = SimpleNamespace() @@ -110,6 +111,7 @@ def __init__(self, module: Module, config: "dict[str, ]", r_env: ResolutionEnv, self.r_env = r_env self.share = share self.bin = bin + self.aux = aux self._getreqmaybe(self.takes, module.takes, config["takes"]) self._getreqmaybe(self.values, module.values, config["values"]) diff --git a/f4pga/flows/platforms.yml b/f4pga/flows/platforms.yml index 6f33517d0..3aa3965aa 100644 --- a/f4pga/flows/platforms.yml +++ b/f4pga/flows/platforms.yml @@ -241,13 +241,13 @@ ql-eos-s3: PINMAP_FILE: '${shareDir}/arch/ql-eos-s3_wlcsp/pinmap_${package}.csv' PCF_FILE: '${:pcf}' PYTHON3: '${python3}' - UTILS_PATH: '${shareDir}/scripts' + UTILS_PATH: '${auxDir}/utils' prepare_sdc: module: 'common:generic_script_wrapper' params: stage_name: prepare_sdc interpreter: '${python3}' - script: ['-m', 'f4pga.utils.quicklogic.process_sdc_constraints'] + script: "${auxDir}/utils/quicklogic/process_sdc_constraints.py" outputs: sdc: mode: file @@ -259,7 +259,7 @@ ql-eos-s3: sdc-out: '${:eblif[noext]}.sdc' pcf: '${:pcf}' pin-map: '' - $PYTHONPATH: '${shareDir}/scripts/' + $PYTHONPATH: "${auxDir}/utils/quicklogic" pack: module: 'common:pack' values: @@ -301,7 +301,7 @@ ql-eos-s3: params: stage_name: ioplace interpreter: '${python3}' - script: ['-m', 'f4pga.utils.quicklogic.pp3.create_ioplace'] + script: "${auxDir}/utils/quicklogic/pp3/create_ioplace.py" outputs: io_place: mode: stdout @@ -311,13 +311,13 @@ ql-eos-s3: net: '${:net}' pcf: '${:pcf}' map: '${shareDir}/arch/ql-eos-s3_wlcsp/pinmap_${package}.csv' - $PYTHONPATH: '${shareDir}/scripts/' + $PYTHONPATH: "${auxDir}/utils/" place_constraints: module: 'common:generic_script_wrapper' params: stage_name: place_constraints interpreter: '${python3}' - script: ['-m', 'f4pga.utils.quicklogic.pp3.create_place_constraints'] + script: "${auxDir}/utils/quicklogic/pp3/create_place_constraints.py" outputs: place_constraints: mode: stdout @@ -326,7 +326,7 @@ ql-eos-s3: blif: '${:eblif}' map: '${shareDir}/arch/ql-eos-s3_wlcsp/clkmap_${package}.csv' i: '${:io_place}' - $PYTHONPATH: '${shareDir}/scripts/' + $PYTHONPATH: "${auxDir}/utils/" place: module: 'common:place' iomux_jlink: @@ -334,7 +334,7 @@ ql-eos-s3: params: stage_name: iomux_jlink interpreter: '${python3}' - script: ['-m', 'f4pga.utils.quicklogic.pp3.eos-s3.iomux_config'] + script: "${auxDir}/utils/quicklogic/pp3/eos-s3/iomux_config.py" outputs: iomux_jlink: mode: stdout @@ -344,13 +344,13 @@ ql-eos-s3: pcf: '${:pcf}' map: '${shareDir}/arch/ql-eos-s3_wlcsp/pinmap_${package}.csv' output-format: jlink - $PYTHONPATH: '${shareDir}/scripts/' + $PYTHONPATH: "${auxDir}/utils/" iomux_openocd: module: 'common:generic_script_wrapper' params: stage_name: iomux_openocd interpreter: '${python3}' - script: ['-m', 'f4pga.utils.quicklogic.pp3.eos-s3.iomux_config'] + script: "${auxDir}/utils/quicklogic/pp3/eos-s3/iomux_config.py" outputs: iomux_openocd: mode: stdout @@ -360,13 +360,13 @@ ql-eos-s3: pcf: '${:pcf}' map: '${shareDir}/arch/ql-eos-s3_wlcsp/pinmap_${package}.csv' output-format: openocd - $PYTHONPATH: '${shareDir}/scripts/' + $PYTHONPATH: "${auxDir}/utils/" iomux_binary: module: 'common:generic_script_wrapper' params: stage_name: iomux_binary interpreter: '${python3}' - script: ['-m', 'f4pga.utils.quicklogic.pp3.eos-s3.iomux_config'] + script: "${auxDir}/utils/quicklogic/pp3/eos-s3/iomux_config.py" outputs: iomux_binary: mode: stdout @@ -376,7 +376,7 @@ ql-eos-s3: pcf: '${:pcf}' map: '${shareDir}/arch/ql-eos-s3_wlcsp/pinmap_${package}.csv' output-format: binary - $PYTHONPATH: '${shareDir}/scripts/' + $PYTHONPATH: "${auxDir}/utils/" route: module: 'common:route' values: @@ -575,6 +575,7 @@ ql-eos-s3: $FPGA_FAM: eos-s3 $PATH: '${shareDir}/../../conda/envs/eos-s3/bin/:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' $BIN_DIR_PATH: '${binDir}' + $PYTHONPATH: "${auxDir}/utils/quicklogic/pp3" ql-k4n8_fast: &ql-k4n8 diff --git a/f4pga/flows/runner.py b/f4pga/flows/runner.py index 92711cbec..c2418d779 100644 --- a/f4pga/flows/runner.py +++ b/f4pga/flows/runner.py @@ -73,11 +73,13 @@ def get_module(path: str): class ModRunCtx: share: str bin: str + aux: str config: "dict[str, ]" - def __init__(self, share: str, bin: str, config: "dict[str, ]"): + def __init__(self, share: str, bin: str, aux: str, config: "dict[str, ]"): self.share = share self.bin = bin + self.aux = aux self.config = config def make_r_env(self): @@ -110,7 +112,7 @@ def module_io(module: Module): def module_map(module: Module, ctx: ModRunCtx): try: - mod_ctx = ModuleContext(module, ctx.config, ctx.make_r_env(), ctx.share, ctx.bin) + mod_ctx = ModuleContext(module, ctx.config, ctx.make_r_env(), ctx.share, ctx.bin, ctx.aux) except Exception as e: raise ModuleFailException(module.name, "map", e) @@ -119,7 +121,7 @@ def module_map(module: Module, ctx: ModRunCtx): def module_exec(module: Module, ctx: ModRunCtx): try: - mod_ctx = ModuleContext(module, ctx.config, ctx.make_r_env(), ctx.share, ctx.bin) + mod_ctx = ModuleContext(module, ctx.config, ctx.make_r_env(), ctx.share, ctx.bin, ctx.aux) except Exception as e: raise ModuleFailException(module.name, "exec", e) diff --git a/f4pga/fpga_map.json b/f4pga/fpga_map.json new file mode 100644 index 000000000..9738b0e7a --- /dev/null +++ b/f4pga/fpga_map.json @@ -0,0 +1,12 @@ +{ + "xc7": { + "manufacturer": "xilinx" + }, + "eos-s3": { + "manufacturer": "quicklogic", + "architecture": "pp3" + }, + "qlf_k4n8": { + "manufacturer": "quicklogic" + } +} diff --git a/f4pga/setup.py b/f4pga/setup.py index 61a286b8d..13f01bc8f 100644 --- a/f4pga/setup.py +++ b/f4pga/setup.py @@ -80,6 +80,9 @@ def get_requirements(file: Path) -> List[str]: url="https://github.com/chipsalliance/f4pga", package_dir={"f4pga": "."}, package_data={ + "f4pga": [ + "fpga_map.json", + ], "f4pga.flows": [ "*.yml", ], diff --git a/f4pga/util.py b/f4pga/util.py new file mode 100644 index 000000000..9ec7e3d23 --- /dev/null +++ b/f4pga/util.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (C) 2022 F4PGA Authors +# +# 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 + +""" +Utility scripts handler +""" + +from subprocess import check_call +from shutil import which +from os import environ, strerror +from pathlib import Path +import errno +import json +import pkgutil + +# import warnings +import importlib + +import f4pga.aux.utils +from f4pga.flows.common import sfprint + +# from f4pga.aux.utils import * + + +class Util: + """ + Wrapper for internal python utils + """ + + def _set_fpga_data(self): + fpga_map = open(self.root_dir / "fpga_map.json") + fmap = json.load(fpga_map) + + self.architectures = fmap.keys() + + for fpga_fam, fpga_data in fmap.items(): + if self.fpga_fam == fpga_fam: + self.manufacturer = fpga_data["manufacturer"] + if "architecture" in fpga_data.keys(): + self.arch = fpga_data["architecture"] + self.subarch = fpga_fam + else: + self.arch = fpga_fam + self.subarch = None + break + + def __init__(self, name, function, args): + self.name = name + self.function = function + self.args = args + self.env = environ.copy() + self.fpga_fam = self.env.get("FPGA_FAM", "xc7") + self.root_dir = Path(__file__).resolve().parent + self._set_fpga_data() + + def _get_util_path(self): + man = self.manufacturer + arch = self.arch + subarch = self.subarch + script_name = self.name + ".py" + + if subarch is not None: + util_path = "f4pga.aux.utils." + man + "." + arch + "." + subarch + "." + self.name + subarch_path = self.root_dir / "aux" / "utils" / man / arch / subarch / script_name + if subarch_path.is_file(): + return util_path + + util_path = "f4pga.aux.utils." + man + "." + arch + "." + self.name + arch_path = self.root_dir / "aux" / "utils" / man / arch / script_name + if arch_path.is_file(): + return util_path + + util_path = "f4pga.aux.utils." + man + "." + self.name + manufacturer_path = self.root_dir / "aux" / "utils" / man / script_name + if manufacturer_path.is_file(): + return util_path + + # Look through other directories common for the manufacturer + manufacturer_path = (self.root_dir / "aux" / "utils" / man).rglob("*") + manufacturer_dirs = [ + man_dir.stem for man_dir in manufacturer_path if (man_dir.is_dir() and man_dir not in self.architectures) + ] + for man_dir in manufacturer_dirs: + util_path = "f4pga.aux.utils." + man + "." + man_dir + "." + self.name + manufacturer_path = self.root_dir / "aux" / "utils" / man / man_dir / script_name + if manufacturer_path.is_file(): + return util_path + + util_path = "f4pga.aux.utils." + self.name + common_path = self.root_dir / "aux" / "utils" / script_name + if common_path.is_file(): + return util_path + else: + if subarch is not None: + raise FileNotFoundError( + errno.ENOENT, + strerror(errno.ENOENT), + str(common_path) + + " or " + + str(manufacturer_path) + + " or " + + str(arch_path) + + " or " + + str(subarch_path), + ) + else: + raise FileNotFoundError( + errno.ENOENT, + strerror(errno.ENOENT), + str(common_path) + " or " + str(manufacturer_path) + " or " + str(arch_path), + ) + return util_path + + def exec(self): + util_path = self._get_util_path() + check_call([which("python3"), "-m", util_path] + self.args, env=self.env) diff --git a/f4pga/wrappers/sh/__init__.py b/f4pga/wrappers/sh/__init__.py index e9a75f1eb..2e1df1fbd 100644 --- a/f4pga/wrappers/sh/__init__.py +++ b/f4pga/wrappers/sh/__init__.py @@ -413,7 +413,7 @@ def generate_constraints(): exit -1 fi - '{python3}' -m f4pga.utils.quicklogic.qlf_k4n8.create_ioplace \ + f4pga utils create_ioplace \ --pcf '{pcf}' \ --blif '{eblif}' \ --pinmap_xml '{archs_dir}'/"${{DEVICE_PATH}}_${{DEVICE_PATH}}/${{PINMAPXML}}" \ @@ -437,14 +437,14 @@ def generate_constraints(): DEVICE_PATH='{device}_wlcsp' PINMAP='{archs_dir}'/"${{DEVICE_PATH}}/${{PINMAPCSV}}" - '{python3}' -m f4pga.utils.quicklogic.pp3.create_ioplace \ + f4pga utils create_ioplace \ --pcf '{pcf}' \ --blif '{eblif}' \ --map "$PINMAP" \ --net '{net}' \ > '{place_file_prefix}_io.place' - '{python3}' -m f4pga.utils.quicklogic.pp3.create_place_constraints \ + f4pga utils create_place_constraints \ --blif '{eblif}' \ --map '{archs_dir}'/"${{DEVICE_PATH}}/${{CLKMAPCSV}}" \ -i '{place_file_prefix}_io.place' \ @@ -456,7 +456,7 @@ def generate_constraints(): + "\n".join( [ f""" - '{python3}' -m f4pga.utils.quicklogic.pp3.eos-s3.iomux_config \ + f4pga utils iomux_config \ --eblif '{eblif}' \ --pcf '{pcf}' \ --map "$PINMAP" \ @@ -687,7 +687,7 @@ def repack(): """ + f""" PYTHONPATH='{F4PGA_SHARE_DIR}/scripts':$PYTHONPATH \ - '{python3}' -m f4pga.utils.quicklogic.repacker.repack \ + f4pga utils repack \ --vpr-arch ${{ARCH_DEF}} \ --repacking-rules ${{ARCH_DIR}}/${{DEVICE_NAME}}.repacking_rules.json \ $JSON_ARGS \ @@ -750,7 +750,7 @@ def generate_libfile(): PINMAP_XML=${ARCH_DIR}/${PINMAPXML} """ + f""" -'{python3}' -m f4pga.utils.quicklogic.create_lib \ +f4pga utils create_lib \ -n "${{DEV}}_0P72_SSM40" \ -m fpga_top \ -c '{part}' \ @@ -794,7 +794,7 @@ def fasm2bels(): p_run_bash_cmds( f""" -'{python3}' -m f4pga.utils.quicklogic.pp3.fasm2bels '{args.bit}' \ +f4pga utils fasm2bels '{args.bit}' \ --phy-db '{F4PGA_SHARE_DIR}/arch/{args.device}_wlcsp/db_phy.pickle' \ --device-name "${{DEVICE/ql-/}}" \ --package-name '{args.part}' \ diff --git a/f4pga/wrappers/sh/quicklogic/ql.f4pga.sh b/f4pga/wrappers/sh/quicklogic/ql.f4pga.sh index b22f86ae6..f2ca78066 100755 --- a/f4pga/wrappers/sh/quicklogic/ql.f4pga.sh +++ b/f4pga/wrappers/sh/quicklogic/ql.f4pga.sh @@ -339,7 +339,7 @@ all: \${BUILDDIR}/\${TOP}.${RUN_TILL}\n\ cd \${BUILDDIR} && symbiflow_synth -t \${TOP} -v \${VERILOG} -F \${FAMILY} -d \${DEVICE} -p \${PCF} -P \${PART} ${COMPILE_EXTRA_ARGS[*]} > $LOG_FILE 2>&1\n\ \n\ \${BUILDDIR}/\${TOP}.sdc: \${BUILDDIR}/\${TOP}.eblif\n\ - python3 -m f4pga.utils.quicklogic.process_sdc_constraints --sdc-in \${SDC_IN} --sdc-out \$@ --pcf \${PCF} --eblif \${BUILDDIR}/\${TOP}.eblif --pin-map \${PINMAP_CSV}\n\ + f4pga utils process_sdc_constraints --sdc-in \${SDC_IN} --sdc-out \$@ --pcf \${PCF} --eblif \${BUILDDIR}/\${TOP}.eblif --pin-map \${PINMAP_CSV}\n\ \n\ \${BUILDDIR}/\${TOP}.net: \${BUILDDIR}/\${TOP}.eblif \${BUILDDIR}/\${TOP}.sdc\n\ cd \${BUILDDIR} && symbiflow_pack -e \${TOP}.eblif -f \${FAMILY} -d \${DEVICE} -s \${SDC} -c \${PNR_CORNER} >> $LOG_FILE 2>&1\n\ diff --git a/f4pga/wrappers/sh/quicklogic/synth.f4pga.sh b/f4pga/wrappers/sh/quicklogic/synth.f4pga.sh index d5465f983..a2622e7b6 100755 --- a/f4pga/wrappers/sh/quicklogic/synth.f4pga.sh +++ b/f4pga/wrappers/sh/quicklogic/synth.f4pga.sh @@ -145,7 +145,7 @@ else fi fi -YOSYS_COMMANDS=`echo ${EXTRA_ARGS[*]} | python3 -m f4pga.utils.quicklogic.convert_compile_opts` +YOSYS_COMMANDS=`echo ${EXTRA_ARGS[*]} | f4pga utils convert_compile_opts` YOSYS_COMMANDS="${YOSYS_COMMANDS//$'\n'/'; '}" LOG=${TOP}_synth.log diff --git a/f4pga/wrappers/tcl/eos-s3.f4pga.tcl b/f4pga/wrappers/tcl/eos-s3.f4pga.tcl index 2fde00c3c..d5f78ef06 100644 --- a/f4pga/wrappers/tcl/eos-s3.f4pga.tcl +++ b/f4pga/wrappers/tcl/eos-s3.f4pga.tcl @@ -175,7 +175,7 @@ stat # Write output JSON, fixup cell names using an external Python script write_json $::env(OUT_JSON).org.json -exec $::env(PYTHON3) -m f4pga.aux.utils.quicklogic.yosys_fixup_cell_names $::env(OUT_JSON).org.json $::env(OUT_JSON) +exec f4pga utils yosys_fixup_cell_names $::env(OUT_JSON).org.json $::env(OUT_JSON) # Read the fixed JSON back and write verilog design -reset