Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fud stages for HLS place and route #1773

Merged
merged 4 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
bcarlet marked this conversation as resolved.
Show resolved Hide resolved
}

exit
Loading