From 09f3453f39363870f1c03ef1c917a0dedb7598df Mon Sep 17 00:00:00 2001 From: Martin Braun Date: Fri, 9 Feb 2024 18:48:09 +0100 Subject: fpga: Update tooling to use image builder instead of make This modifies repeat_fpga_build.py such that it can take a YAML file as input, not just a make target. It will then use the image builder to prepare the build and generate a valid make command, which in turn is used to build FPGA images repeatedly. --- fpga/usrp3/tools/utils/repeat_fpga_build.py | 71 +++++++++++++++++++++++++---- host/python/uhd/imgbuilder/image_builder.py | 25 +++++----- 2 files changed, 75 insertions(+), 21 deletions(-) diff --git a/fpga/usrp3/tools/utils/repeat_fpga_build.py b/fpga/usrp3/tools/utils/repeat_fpga_build.py index dec734a26..7f013ec61 100755 --- a/fpga/usrp3/tools/utils/repeat_fpga_build.py +++ b/fpga/usrp3/tools/utils/repeat_fpga_build.py @@ -33,9 +33,15 @@ def parse_args(): "--target", "-t", type=str, - required=True, help="FPGA make target to build (e.g., X310_XG).", ) + parser.add_argument( + "--image-core", + "-y", + type=str, + help="For using the image builder instead of make, use this to specify " + "the image core YAML.", + ) parser.add_argument( "--num", "-n", @@ -65,12 +71,52 @@ def parse_args(): return parser.parse_args() -def run_fpga_build(build_seed, target): +def prepare_build(target, image_core): + """ + Run tasks to prepare the build. In particular, execute the RFNoC image builder + if desired. + """ + if image_core: + logging.info("Calling rfnoc_image_builder to prepare FPGA build.") + cmd = [ + "rfnoc_image_builder", + "--yaml-config", + image_core, + "--generate-only", + "--no-hash", + "--no-date", + ] + if target: + cmd += ["--target", target] + result = subprocess.run( + cmd, + check=False, + encoding="utf-8", + capture_output=True, + ) + logging.info("rfnoc_image_builder output:") + logging.info("stdout:\n%s", result.stdout) + logging.info("stderr:\n%s", result.stderr) + if result.returncode: + logging.error("Image builder failed! Consult output for details.") + result.check_returncode() + # Parse the image builder output to get the make command we need to build the FPGA + make_command = re.search(r"(?<=: )make.*$", result.stderr, flags=re.M).group(0) + logging.info("Using make command: %s", make_command) + else: + assert target + make_command = f"make {target}" + return { + "make_command": make_command, + } + + +def run_fpga_build(build_seed, cfg): """Performs one iteration of an FPGA build. Args: build_seed: 32-bit signed integer to seed the FPGA build. - target: Make target name (e.g., X310_XG). + cfg: Build configuration info Returns: 0: The build succeeded. @@ -78,7 +124,8 @@ def run_fpga_build(build_seed, target): 2: There was some other error that should cause us to stop trying. """ output = "" - cmd = f'/bin/bash -c "make {target} BUILD_SEED={build_seed}"' + make_cmd = cfg["make_command"] + cmd = f'/bin/bash -c "{make_cmd} BUILD_SEED={build_seed}"' with subprocess.Popen( cmd, shell=True, @@ -134,17 +181,21 @@ def main(): logging.basicConfig(format="[REPEAT BUILD][%(levelname)s] %(message)s") logging.root.setLevel(logging.INFO) args = parse_args() + if not args.target and not args.image_core: + logging.error("Either --target or --image-core must be provided!") + return 1 build_seed = args.seed status = 128 + cfg = prepare_build(args.target, args.image_core) try: for build_num in range(1, args.num + 1): - logging.info(f"Starting FPGA build {build_num} with seed {build_seed}") - status = run_fpga_build(build_seed, args.target) - logging.info(f"Finished FPGA build {build_num}") + logging.info("Starting FPGA build %d with seed %s", build_num, build_seed) + status = run_fpga_build(build_seed, cfg) + logging.info("Finished FPGA build %d", build_num) if status == 0: - logging.info(f"FPBA build succeeded on attempt number {build_num}") + logging.info("FPGA build succeeded on attempt number %s", build_num) break - elif build_num == args.num: + if build_num == args.num: logging.error("Reached maximum number of FPGA build attempts") elif status == 1: logging.info("FPGA build will be restarted due to unsuccessful attempt") @@ -152,7 +203,7 @@ def main(): logging.error("Stopping due to unexpected FPGA build error") break build_seed = next_build_seed(build_seed) - except (KeyboardInterrupt): + except KeyboardInterrupt: logging.info("Received SIGINT. Aborting . . .") # Return normal Bash value for SIGINT (128+2) return 130 diff --git a/host/python/uhd/imgbuilder/image_builder.py b/host/python/uhd/imgbuilder/image_builder.py index 14d15027a..3228e57c2 100644 --- a/host/python/uhd/imgbuilder/image_builder.py +++ b/host/python/uhd/imgbuilder/image_builder.py @@ -977,26 +977,33 @@ def build(fpga_path, device, image_core_path, edge_file, **args): ] + args.get("extra_makefile_srcs", []) logging.debug("Temporarily changing working directory to %s", build_dir) os.chdir(build_dir) - make_cmd = ". ./setupenv.sh " + setup_cmd = ". ./setupenv.sh " if "vivado_path" in args and args["vivado_path"]: - make_cmd = make_cmd + "--vivado-path=" + args["vivado_path"] + " " + setup_cmd += "--vivado-path=" + args["vivado_path"] + " " + make_cmd = "" if "clean_all" in args and args["clean_all"]: - make_cmd = make_cmd + "&& make cleanall " + make_cmd += "make cleanall && " target = args["target"] if "target" in args else "" - make_cmd = make_cmd + "&& make " + default_target(device, target) - make_cmd += " IMAGE_CORE={} EDGE_FILE={}".format(image_core_path, - edge_file) + make_cmd += "make " + default_target(device, target) + make_cmd += f" IMAGE_CORE={image_core_path} EDGE_FILE={edge_file}" if makefile_src_paths: make_cmd += " RFNOC_OOT_MAKEFILE_SRCS=" + "\\ ".join(makefile_src_paths) if "GUI" in args and args["GUI"]: make_cmd = make_cmd + " GUI=1" + + if args.get('generate_only'): + logging.info("Skip build (generate only option given)") + logging.info("Use the following command to build the image: %s", make_cmd) + return 0 + + make_cmd = setup_cmd + " && " + make_cmd logging.info("Launching build with the following settings:") logging.info(" * Build Directory: %s", build_dir) logging.info(" * Target: %s", target) logging.info(" * Image Core File: %s", image_core_path) logging.info(" * Edge Table File: %s", edge_file) # Wrap it into a bash call: - make_cmd = '{bash} -c "{cmd}"'.format(bash=BASH_EXECUTABLE, cmd=make_cmd) + make_cmd = f'{BASH_EXECUTABLE} -c "{make_cmd}"' logging.debug("Executing the following command: %s", make_cmd) ret_val = os.system(make_cmd) os.chdir(cwd) @@ -1134,10 +1141,6 @@ def build_image(config, fpga_path, config_path, device, **args): ) write_build_env() - if "generate_only" in args and args["generate_only"]: - logging.info("Skip build (generate only option given)") - return 0 - # Check if the YAML files require additional Makefile.srcs extra_makefile_srcs = set() for block_info in builder_conf.noc_blocks.values(): -- cgit v1.2.3-59-g8ed1b