Skip to content

Commit

Permalink
fud stages for HLS place and route (#1773)
Browse files Browse the repository at this point in the history
* Stages for HLS place and route

* tclargs doesn't allow flags

* Add missing description

* Documentation
  • Loading branch information
bcarlet committed Nov 13, 2023
1 parent 4ed26dd commit c4968ce
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 26 deletions.
2 changes: 2 additions & 0 deletions fud/fud/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
4 changes: 4 additions & 0 deletions fud/fud/stages/vivado/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
VivadoExtractStage,
VivadoHLSStage,
VivadoHLSExtractStage,
VivadoHLSPlaceAndRouteStage,
VivadoHLSPlaceAndRouteExtractStage,
)

__all__ = [
"VivadoStage",
"VivadoExtractStage",
"VivadoHLSStage",
"VivadoHLSExtractStage",
"VivadoHLSPlaceAndRouteStage",
"VivadoHLSPlaceAndRouteExtractStage",
]
47 changes: 31 additions & 16 deletions fud/fud/stages/vivado/extract.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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,
Expand All @@ -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:
Expand All @@ -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")
Expand Down Expand Up @@ -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(
{
Expand Down
91 changes: 83 additions & 8 deletions fud/fud/stages/vivado/stage.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import shutil
from pathlib import Path
from pathlib import Path, PurePath
import os

from fud.stages import SourceType, Stage
from fud.stages.remote_context import RemoteExecution
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):
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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"
Expand All @@ -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)

Expand All @@ -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)
38 changes: 36 additions & 2 deletions fud/synth/hls.tcl
Original file line number Diff line number Diff line change
@@ -1,12 +1,46 @@
# Usage: vivado_hls -f hls.tcl -tclargs [impl] [top <name>]

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

# 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

0 comments on commit c4968ce

Please sign in to comment.