From c4968ce570876ddfd0ff9cd401026cf7f4a47537 Mon Sep 17 00:00:00 2001 From: bcarlet <8906114+bcarlet@users.noreply.github.com> Date: Sun, 12 Nov 2023 20:11:11 -0500 Subject: [PATCH] `fud` stages for HLS place and route (#1773) * Stages for HLS place and route * tclargs doesn't allow flags * Add missing description * Documentation --- fud/fud/main.py | 2 + fud/fud/stages/vivado/__init__.py | 4 ++ fud/fud/stages/vivado/extract.py | 47 ++++++++++------ fud/fud/stages/vivado/stage.py | 91 ++++++++++++++++++++++++++++--- fud/synth/hls.tcl | 38 ++++++++++++- 5 files changed, 156 insertions(+), 26 deletions(-) diff --git a/fud/fud/main.py b/fud/fud/main.py index 59c0f07e90..5b89d86bcb 100644 --- a/fud/fud/main.py +++ b/fud/fud/main.py @@ -135,6 +135,8 @@ def register_stages(registry): registry.register(vivado.VivadoExtractStage()) registry.register(vivado.VivadoHLSStage()) registry.register(vivado.VivadoHLSExtractStage()) + registry.register(vivado.VivadoHLSPlaceAndRouteStage()) + registry.register(vivado.VivadoHLSPlaceAndRouteExtractStage()) # Vcdump registry.register(vcdump.VcdumpStage()) diff --git a/fud/fud/stages/vivado/__init__.py b/fud/fud/stages/vivado/__init__.py index 3f46c0a87b..e041f517d3 100644 --- a/fud/fud/stages/vivado/__init__.py +++ b/fud/fud/stages/vivado/__init__.py @@ -3,6 +3,8 @@ VivadoExtractStage, VivadoHLSStage, VivadoHLSExtractStage, + VivadoHLSPlaceAndRouteStage, + VivadoHLSPlaceAndRouteExtractStage, ) __all__ = [ @@ -10,4 +12,6 @@ "VivadoExtractStage", "VivadoHLSStage", "VivadoHLSExtractStage", + "VivadoHLSPlaceAndRouteStage", + "VivadoHLSPlaceAndRouteExtractStage", ] diff --git a/fud/fud/stages/vivado/extract.py b/fud/fud/stages/vivado/extract.py index bb0cc0d232..964e95ee24 100644 --- a/fud/fud/stages/vivado/extract.py +++ b/fud/fud/stages/vivado/extract.py @@ -1,6 +1,6 @@ import json import os -from pathlib import Path +from pathlib import Path, PurePath import re import traceback import logging as log @@ -37,9 +37,9 @@ def file_contains(regex, filename): return len(strings) == 0 -def rtl_component_extract(directory, name): +def rtl_component_extract(file: Path, name: str): try: - with (directory / "synth_1" / "runme.log").open() as f: + with file.open() as f: log = f.read() comp_usage = re.search( r"Start RTL Component Statistics(.*?)Finished RTL", log, re.DOTALL @@ -52,21 +52,30 @@ def rtl_component_extract(directory, name): return 0 -def futil_extract(directory): - # Search for directory named FutilBuild.runs +def place_and_route_extract( + directory: Path, + files_root: str, + utilization_file: PurePath, + timing_file: PurePath, + synthesis_file: PurePath, +): + # Search for the given root directory for root, dirs, _ in os.walk(directory): for d in dirs: - if d == "FutilBuild.runs": + if d == files_root: directory = Path(os.path.join(root, d)) break + util_file = directory / utilization_file + synth_file = directory / synthesis_file + timing_file = directory / timing_file + # The resource information is extracted first for the implementation files, and # then for the synthesis files. This is done separately in case users want to # solely use one or the other. resource_info = {} # Extract utilization information - util_file = directory / "impl_1" / "main_utilization_placed.rpt" try: if util_file.exists(): impl_parser = rpt.RPTParser(util_file) @@ -87,8 +96,8 @@ def futil_extract(directory): find_row(slice_logic, "Site Type", "CLB LUTs")["Used"] ), "dsp": to_int(find_row(dsp_table, "Site Type", "DSPs")["Used"]), - "registers": rtl_component_extract(directory, "Registers"), - "muxes": rtl_component_extract(directory, "Muxes"), + "registers": rtl_component_extract(synth_file, "Registers"), + "muxes": rtl_component_extract(synth_file, "Muxes"), "clb_registers": clb_reg, "carry8": carry8, "f7_muxes": f7_muxes, @@ -105,7 +114,6 @@ def futil_extract(directory): log.error("Failed to extract utilization information") # Get timing information - timing_file = directory / "impl_1" / "main_timing_summary_routed.rpt" if not timing_file.exists(): log.error(f"Timing file {timing_file} is missing") else: @@ -132,7 +140,6 @@ def futil_extract(directory): ) # Extraction for synthesis files. - synth_file = directory / "synth_1" / "runme.log" try: if not synth_file.exists(): log.error(f"Synthesis file {synth_file} is missing") @@ -169,18 +176,26 @@ def futil_extract(directory): return json.dumps(resource_info, indent=2) -def hls_extract(directory): - directory = directory / "benchmark.prj" / "solution1" +def hls_extract(directory: Path, top: str): + # Search for directory named benchmark.prj + for root, dirs, _ in os.walk(directory): + for d in dirs: + if d == "benchmark.prj": + directory = Path(os.path.join(root, d)) + break + + directory = directory / "solution1" + try: - parser = rpt.RPTParser(directory / "syn" / "report" / "kernel_csynth.rpt") + parser = rpt.RPTParser(directory / "syn" / "report" / f"{top}_csynth.rpt") summary_table = parser.get_table(re.compile(r"== Utilization Estimates"), 2) instance_table = parser.get_table(re.compile(r"\* Instance:"), 0) solution_data = json.load((directory / "solution1_data.json").open()) - latency = solution_data["ModuleInfo"]["Metrics"]["kernel"]["Latency"] + latency = solution_data["ModuleInfo"]["Metrics"][top]["Latency"] total_row = find_row(summary_table, "Name", "Total") - s_axi_row = find_row(instance_table, "Instance", "kernel_control_s_axi_U") + s_axi_row = find_row(instance_table, "Instance", f"{top}_control_s_axi_U") return json.dumps( { diff --git a/fud/fud/stages/vivado/stage.py b/fud/fud/stages/vivado/stage.py index 4256ef919b..775f5436c2 100644 --- a/fud/fud/stages/vivado/stage.py +++ b/fud/fud/stages/vivado/stage.py @@ -1,5 +1,5 @@ import shutil -from pathlib import Path +from pathlib import Path, PurePath import os from fud.stages import SourceType, Stage @@ -7,7 +7,7 @@ from fud.utils import TmpDir, shell from fud import config as cfg -from .extract import futil_extract, hls_extract +from .extract import hls_extract, place_and_route_extract class VivadoBaseStage(Stage): @@ -42,12 +42,19 @@ def device_files(self, config): """ pass + def extra_flags(self, config): + """ + Extra flags to append to the command. + """ + return "" + def _define_steps(self, verilog_path, builder, config): use_ssh = bool(config.get(["stages", self.name, "remote"])) + flags = f"{self.flags} {self.extra_flags(config)}" if use_ssh: - cmd = f"{config['stages', self.name, 'exec']} {self.flags}" + cmd = f"{config['stages', self.name, 'exec']} {flags}" else: - cmd = f"{self.remote_exec} {self.flags}" + cmd = f"{self.remote_exec} {flags}" # Steps and schedule local_tmpdir = self.setup_environment(verilog_path, builder, config) @@ -149,10 +156,39 @@ def __init__(self): def device_files(self, config): root = Path(config["global", cfg.ROOT]) return [ - str(root / "fud" / "synth" / "hls.tcl"), - str(root / "fud" / "synth" / "fxp_sqrt.h"), + root / "fud" / "synth" / "hls.tcl", + root / "fud" / "synth" / "fxp_sqrt.h", + ] + + def extra_flags(self, config): + top = config.get(["stages", self.name, "top"]) + return f"-tclargs top {top}" if top else "" + + +class VivadoHLSPlaceAndRouteStage(VivadoBaseStage): + name = "vivado-hls" + + def __init__(self): + super().__init__( + "vivado-hls", + "hls-files-routed", + "Performs placement and routing of RTL generated by Vivado HLS", + target_name="kernel.cpp", + remote_exec="vivado_hls", + flags="-f hls.tcl -tclargs impl", + ) + + def device_files(self, config): + root = Path(config["global", cfg.ROOT]) + return [ + root / "fud" / "synth" / "hls.tcl", + root / "fud" / "synth" / "fxp_sqrt.h", ] + def extra_flags(self, config): + top = config.get(["stages", self.name, "top"]) + return f"top {top}" if top else "" + class VivadoExtractStage(Stage): name = "synth-files" @@ -172,7 +208,13 @@ def extract(directory: SourceType.Directory) -> SourceType.String: """ Extract relevant data from Vivado synthesis files. """ - return futil_extract(Path(directory.name)) + return place_and_route_extract( + Path(directory.name), + "FutilBuild.runs", + PurePath("impl_1", "main_utilization_placed.rpt"), + PurePath("impl_1", "main_timing_summary_routed.rpt"), + PurePath("synth_1", "runme.log"), + ) return extract(input) @@ -195,6 +237,39 @@ def extract(directory: SourceType.Directory) -> SourceType.String: """ Extract relevant data from Vivado synthesis files. """ - return hls_extract(Path(directory.name)) + top = config.get(["stages", self.name, "top"]) or "kernel" + return hls_extract(Path(directory.name), top) + + return extract(input) + + +class VivadoHLSPlaceAndRouteExtractStage(Stage): + name = "hls-files-routed" + + def __init__(self): + super().__init__( + src_state="hls-files-routed", + target_state="hls-detailed-estimate", + input_type=SourceType.Directory, + output_type=SourceType.String, + description="Extracts information from Vivado HLS synthesis files", + ) + + def _define_steps(self, input, builder, config): + @builder.step() + def extract(directory: SourceType.Directory) -> SourceType.String: + """ + Extract relevant data from Vivado synthesis files. + """ + top = config.get(["stages", self.name, "top"]) or "kernel" + verilog_dir = PurePath("solution1", "impl", "verilog") + + return place_and_route_extract( + Path(directory.name), + "benchmark.prj", + verilog_dir / "report" / f"{top}_utilization_routed.rpt", + verilog_dir / "report" / f"{top}_timing_routed.rpt", + verilog_dir / "project.runs" / "bd_0_hls_inst_0_synth_1" / "runme.log", + ) return extract(input) diff --git a/fud/synth/hls.tcl b/fud/synth/hls.tcl index a99576340f..561c5d08ff 100644 --- a/fud/synth/hls.tcl +++ b/fud/synth/hls.tcl @@ -1,7 +1,32 @@ +# Usage: vivado_hls -f hls.tcl -tclargs [impl] [top ] + +proc lshift listVar { + upvar 1 $listVar l + set r [lindex $l 0] + set l [lreplace $l [set l 0] 0] + return $r +} + +set impl 0 +set top kernel set hls_prj benchmark.prj + +while {[llength $argv]} { + set flag [lshift argv] + switch -exact -- $flag { + impl { + set impl 1 + } + top { + set top [lshift argv] + } + } +} + open_project ${hls_prj} -reset -set_top kernel; # The name of the hardware function. -add_files [glob ./*.cpp] -cflags "-std=c++11 -DVHLS" ; # HLS source files. +set_top $top; # The name of the hardware function. +add_files [glob ./*.cpp] -cflags "-std=c++11 -DVHLS"; # HLS source files. + open_solution "solution1" set_part xczu3eg-sbva484-1-e create_clock -period 7 @@ -9,4 +34,13 @@ create_clock -period 7 # Actions we can take include csim_design, csynth_design, or cosim_design. csynth_design +if {$impl} { + # Exporting the design gives us a convenient way to run place and route via + # the `-flow impl` option. Another option is `-flow syn` if you only need to + # run RTL synthesis. + # The packaging options don't matter for our purposes, but we set the version + # to 1.1.0 to circumvent this bug: https://support.xilinx.com/s/article/76960 + export_design -format ip_catalog -version 1.1.0 -flow impl +} + exit