Skip to content

Commit 0f4a7b9

Browse files
committed
Fixed synthesis reporting for Single- and Multi-FPGA
1 parent ebd8673 commit 0f4a7b9

11 files changed

Lines changed: 179 additions & 84 deletions

File tree

src/finn/builder/build_dataflow_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ def _fix_path(p: Path | None) -> Path | None:
451451

452452
#: Which output(s) to generate from the build flow. See documentation of
453453
#: DataflowOutputType for available options.
454-
generate_outputs: Optional[list[DataflowOutputType]] = field(
454+
generate_outputs: list[DataflowOutputType] = field(
455455
default_factory=lambda: [
456456
DataflowOutputType.STITCHED_IP,
457457
DataflowOutputType.ESTIMATE_REPORTS,

src/finn/builder/build_dataflow_steps.py

Lines changed: 88 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,12 @@
141141
from finn.transformation.streamline import Streamline
142142
from finn.transformation.streamline.reorder import MakeMaxPoolNHWC
143143
from finn.transformation.streamline.round_thresholds import RoundAndClipThresholds
144-
from finn.util.basic import get_liveness_threshold_cycles, get_rtlsim_trace_depth
144+
from finn.util.basic import (
145+
get_liveness_threshold_cycles,
146+
get_metadata_prop_path,
147+
get_metadata_prop_safe,
148+
get_rtlsim_trace_depth,
149+
)
145150
from finn.util.config import extract_model_config_consolidate_shuffles, extract_model_config_to_json
146151
from finn.util.exception import FINNMultiFPGAUserError, FINNUserError
147152
from finn.util.execution import execute_parent
@@ -1604,59 +1609,94 @@ def step_vivado_power_estimation(model: ModelWrapper, cfg: DataflowBuildConfig):
16041609
def step_synthesize_bitfile(model: ModelWrapper, cfg: DataflowBuildConfig):
16051610
"""Synthesize a bitfile for the using the specified shell flow, using either
16061611
Vivado or Vitis, to target the specified board."""
1612+
if DataflowOutputType.BITFILE not in cfg.generate_outputs:
1613+
log.warning(
1614+
"DataflowOutputType.BITFILE not in requested outputs, skipping step_synthesize_bitfile."
1615+
)
1616+
return model
16071617

1608-
if DataflowOutputType.BITFILE in cfg.generate_outputs:
1609-
bitfile_dir = cfg.output_dir + "/bitfile"
1610-
os.makedirs(bitfile_dir, exist_ok=True)
1611-
report_dir = cfg.output_dir + "/report"
1612-
os.makedirs(report_dir, exist_ok=True)
1613-
partition_model_dir = cfg.output_dir + "/intermediate_models/kernel_partitions"
1614-
if cfg.shell_flow_type == ShellFlowType.VIVADO_ZYNQ:
1615-
model = model.transform(
1616-
ZynqBuild(
1617-
cfg.board,
1618-
cfg.synth_clk_period_ns,
1619-
cfg.enable_hw_debug,
1620-
cfg.enable_instrumentation,
1621-
cfg.instrumentation_no_dma,
1622-
cfg.live_fifo_sizing,
1623-
partition_model_dir=partition_model_dir,
1624-
)
1625-
)
1626-
1627-
bitfile_path = os.path.join(bitfile_dir, "finn-accel.bit")
1628-
copy(model.get_metadata_prop("bitfile"), bitfile_path)
1629-
copy(model.get_metadata_prop("hw_handoff"), bitfile_dir + "/finn-accel.hwh")
1630-
copy(
1631-
model.get_metadata_prop("vivado_synth_rpt"),
1632-
report_dir + "/post_synth_resources.xml",
1633-
)
1634-
1635-
model.set_metadata_prop("bitfile_output", os.path.abspath(bitfile_path))
1636-
1637-
post_synth_resources = model.analysis(post_synth_res)
1638-
with open(report_dir + "/post_synth_resources.json", "w") as f:
1639-
json.dump(post_synth_resources, f, indent=2)
1618+
# Create some directories for later
1619+
bitfile_dir = Path(cfg.output_dir) / "bitfile"
1620+
bitfile_dir.mkdir(exist_ok=True)
1621+
report_dir = Path(cfg.output_dir) / "report"
1622+
report_dir.mkdir(exist_ok=True)
1623+
partition_model_dir = Path(cfg.output_dir) / "intermediate_models/kernel_partitions"
16401624

1641-
vivado_pynq_proj_dir = model.get_metadata_prop("vivado_pynq_proj")
1642-
timing_rpt = (
1643-
"%s/finn_zynq_link.runs/impl_1/top_wrapper_timing_summary_routed.rpt"
1644-
% vivado_pynq_proj_dir
1625+
# The actual synthesis step!
1626+
if cfg.shell_flow_type == ShellFlowType.VIVADO_ZYNQ:
1627+
model = model.transform(
1628+
ZynqBuild(
1629+
cfg.board,
1630+
cfg.synth_clk_period_ns,
1631+
cfg.enable_hw_debug,
1632+
cfg.enable_instrumentation,
1633+
cfg.instrumentation_no_dma,
1634+
cfg.live_fifo_sizing,
1635+
partition_model_dir=partition_model_dir,
16451636
)
1646-
copy(timing_rpt, report_dir + "/post_route_timing.rpt")
1647-
1648-
elif cfg.shell_flow_type == ShellFlowType.VITIS_ALVEO:
1649-
# TODO: Rework missing parts here
1650-
model = model.transform(VitisBuild(cfg))
1651-
1637+
)
1638+
elif cfg.shell_flow_type == ShellFlowType.VITIS_ALVEO:
1639+
model = model.transform(VitisBuild(cfg))
1640+
else:
1641+
raise Exception("Unrecognized shell_flow_type: " + str(cfg.shell_flow_type))
1642+
1643+
# Store bitstreams into the output directory. This is the same regardless of flow.
1644+
bitfile_json = json.loads(get_metadata_prop_safe(model, "bitfile"))
1645+
suffix = ""
1646+
if cfg.shell_flow_type == ShellFlowType.VIVADO_ZYNQ:
1647+
suffix = ".bit"
1648+
elif cfg.shell_flow_type == ShellFlowType.VITIS_ALVEO:
1649+
suffix = ".xclbin"
1650+
1651+
# If only one device, omit the device suffix
1652+
if len(list(bitfile_json.keys())) == 1:
1653+
bitfile_path = bitfile_dir / f"finn-accel{suffix}"
1654+
copy(bitfile_json[0], bitfile_path)
1655+
# For the single-fpga case, store in bitfile_output
1656+
# TODO: Also adapt this for Multi-FGPA
1657+
model.set_metadata_prop("bitfile_output", str(bitfile_path.absolute()))
1658+
else:
1659+
for device, path in bitfile_json.items():
1660+
copy(path, bitfile_dir / f"finn-accel-{device}{suffix}")
1661+
1662+
# Store synth reports
1663+
rpt_json = json.loads(get_metadata_prop_safe(model, "vivado_synth_rpt"))
1664+
if len(list(rpt_json.keys())) == 1:
1665+
res_report = Path(rpt_json[0])
1666+
if res_report.exists():
1667+
copy(res_report, report_dir / "post_synth_resources.xml")
16521668
else:
1653-
raise Exception("Unrecognized shell_flow_type: " + str(cfg.shell_flow_type))
1654-
log.info(f"Bitfile written into {bitfile_dir}")
1655-
1669+
log.warning(f"Resource report XML not found: {res_report}")
16561670
else:
1657-
log.info(
1658-
"DataflowOutputType.BITFILE not in requested outputs, skipping step_synthesize_bitfile."
1671+
for device, path in rpt_json.items():
1672+
if path.exists():
1673+
copy(path, report_dir / f"post_synth_resources_{device}.xml")
1674+
else:
1675+
log.warning(f"Resource report XML not found: {path}")
1676+
1677+
# Store artifacts, depending on flow
1678+
if cfg.shell_flow_type == ShellFlowType.VIVADO_ZYNQ:
1679+
# Store synthesis artifacts
1680+
hw_handoff = get_metadata_prop_path(model, "hw_handoff", must_exist=True)
1681+
copy(hw_handoff, bitfile_dir / "finn-accel.hwh")
1682+
1683+
# Log post synthesis resource as a JSON file
1684+
post_synth_resources = model.analysis(post_synth_res)
1685+
(report_dir / "post_synth_resources.json").write_text(
1686+
json.dumps(post_synth_resources, indent=2)
1687+
)
1688+
1689+
# Store the post-route timing report
1690+
timing_report = (
1691+
get_metadata_prop_path(model, "vivado_pynq_proj", must_exist=True)
1692+
/ "finn_zynq_link.runs"
1693+
/ "impl_1"
1694+
/ "top_wrapper_timing_summary_routed.rpt"
16591695
)
1696+
copy(timing_report, report_dir / "post_route_timing.rpt")
1697+
1698+
elif cfg.shell_flow_type == ShellFlowType.VITIS_ALVEO:
1699+
pass
16601700

16611701
return model
16621702

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
open_project {{ VITIS_PROJ_PATH }}/_x/link/vivado/vpl/prj/prj.xpr
2+
open_run impl_1
3+
report_utilization -hierarchical -hierarchical_depth 5 -file {{ VITIS_PROJ_PATH }}/synth_report.xml -format xml

src/finn/transformation/fpgadataflow/make_zynq_proj.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -588,8 +588,10 @@ def apply(self, model):
588588
)
589589
deploy_bitfile_name = vivado_pynq_proj_dir + "/resizer.bit"
590590
copy(bitfile_name, deploy_bitfile_name)
591-
# set bitfile attribute
592-
model.set_metadata_prop("bitfile", deploy_bitfile_name)
591+
592+
# set bitfile attribute (device - path mapping to be in sync with the alveo flow,
593+
# which needs to support multiple bitstream output files due to Multi-FPGA
594+
model.set_metadata_prop("bitfile", json.dumps({0: deploy_bitfile_name}))
593595
hwh_name_alts = [
594596
vivado_pynq_proj_dir + "/finn_zynq_link.srcs/sources_1/bd/top/hw_handoff/top.hwh",
595597
vivado_pynq_proj_dir + "/finn_zynq_link.gen/sources_1/bd/top/hw_handoff/top.hwh",
@@ -608,7 +610,8 @@ def apply(self, model):
608610
model.set_metadata_prop("hw_handoff", deploy_hwh_name)
609611
# filename for the synth utilization report
610612
synth_report_filename = vivado_pynq_proj_dir + "/synth_report.xml"
611-
model.set_metadata_prop("vivado_synth_rpt", synth_report_filename)
613+
# Stored as a json for Multi-FPGA support (on the alveo side)
614+
model.set_metadata_prop("vivado_synth_rpt", json.dumps({0: synth_report_filename}))
612615
return (model, False)
613616

614617

src/finn/transformation/fpgadataflow/multifpga/aurora/metadata.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from typing import TYPE_CHECKING, cast
1212

1313
from finn.transformation.fpgadataflow.multifpga.metadata import DataDirection, NetworkMetadata
14+
from finn.util.basic import get_metadata_prop_path
1415
from finn.util.exception import FINNInternalError, FINNMultiFPGAConfigError
1516

1617
if TYPE_CHECKING:
@@ -60,15 +61,15 @@ class AuroraNetworkMetadata(NetworkMetadata, DataClassYAMLMixin):
6061
@staticmethod
6162
def from_model(model: ModelWrapper) -> AuroraNetworkMetadata:
6263
"""Load metadata from the model."""
63-
p = model.get_metadata_prop("network_metadata")
64-
if p is None:
65-
raise FINNInternalError(
64+
p = get_metadata_prop_path(
65+
model,
66+
"network_metadata",
67+
must_exist=True,
68+
custom_error=(
6669
"Cannot load metadata from model, since no 'network_metadata' prop was set. "
6770
"Did you forget to run the 'AssignMetadata' transformation before this?"
68-
)
69-
p = Path(p)
70-
if not p.exists():
71-
raise FINNInternalError(f"Cannot load metadata. File not found: {p}.")
71+
),
72+
)
7273
meta = AuroraNetworkMetadata.load(p)
7374
meta.loaded_from_path = p
7475
return meta

src/finn/transformation/fpgadataflow/multifpga/create_multi_sdp.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,8 @@
1010

1111
from finn.builder.build_dataflow_config import MFVerbosity
1212
from finn.transformation.fpgadataflow.create_dataflow_partition import CreateDataflowPartition
13-
from finn.transformation.fpgadataflow.multifpga.utils import (
14-
get_device_id,
15-
get_submodel,
16-
set_device_id,
17-
)
1813
from finn.util.exception import FINNMultiFPGAUserError
14+
from finn.util.fpgadataflow import get_device_id, get_submodel, set_device_id
1915
from finn.util.logging import log
2016

2117
if TYPE_CHECKING:
@@ -127,7 +123,7 @@ def apply(self, model: ModelWrapper) -> tuple[ModelWrapper, bool]: # noqa
127123

128124
# Set the SDP's device_id
129125
for node in model.graph.node:
130-
device_id = get_device_id(get_submodel(node).graph.node[0])
126+
device_id = get_device_id(get_submodel(node)[0].graph.node[0])
131127
assert device_id is not None
132128
set_device_id(node, device_id)
133129
return model, False

src/finn/transformation/fpgadataflow/multifpga/metadata.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
from typing import TYPE_CHECKING, Any
1111

1212
from finn.builder.build_dataflow_config import MFVerbosity
13-
from finn.transformation.fpgadataflow.multifpga.utils import get_device_id
1413
from finn.util.basic import make_build_dir
1514
from finn.util.exception import FINNMultiFPGAError
15+
from finn.util.fpgadataflow import get_device_id
1616
from finn.util.logging import log
1717

1818
if TYPE_CHECKING:

src/finn/transformation/fpgadataflow/multifpga/partition_model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
is_single_in_out_model,
2323
)
2424
from finn.transformation.fpgadataflow.multifpga.partitioner import Partitioner
25-
from finn.transformation.fpgadataflow.multifpga.utils import set_device_id
2625
from finn.util.basic import make_build_dir
2726
from finn.util.exception import (
2827
FINNInternalError,
@@ -31,6 +30,7 @@
3130
FINNMultiFPGANoPartitionerSolutionError,
3231
FINNMultiFPGAUserError,
3332
)
33+
from finn.util.fpgadataflow import set_device_id
3434
from finn.util.logging import LogDisabledConsole, log
3535
from finn.util.platforms import platforms
3636
from finn.util.resources import available_resources, get_estimated_model_resources

src/finn/transformation/fpgadataflow/vitis_build.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,17 @@ def link(self, config: VitisLinkConfiguration) -> Path:
7878
f"at {config.run_script_path.parent}.",
7979
config.run_script_path.parent / "v++_a.log",
8080
)
81+
82+
# Try to generate resource reports, but don't raise an exception if this fails
83+
gen_result = subprocess.run(
84+
shlex.split(f"vivado -mode batch -source {config.gen_report_xml_path}"),
85+
cwd=config.gen_report_xml_path.parent,
86+
)
87+
if gen_result.returncode != 0:
88+
log.error(
89+
f"Could not create post-synthesis resource report in "
90+
f"{config.gen_report_xml_path.parent}. Continuing now."
91+
)
8192
return config.run_script_path.parent / "a.xclbin"
8293

8394
def apply(self, model: ModelWrapper) -> tuple[ModelWrapper, bool]: # noqa
@@ -105,16 +116,19 @@ def apply(self, model: ModelWrapper) -> tuple[ModelWrapper, bool]: # noqa
105116
results = {}
106117
for i, future in futures.items():
107118
result = cast("Path", future.result())
108-
results[i] = str(result)
119+
results[i] = str(result.absolute())
109120
if not result.exists():
110121
log.critical(
111122
f"XCLBIN for device {i} not found. Check "
112123
f"synthesis logs at {configs[i].run_script_path.parent}"
113124
)
114125

115126
# Store paths for usage in driver generation, etc.
116-
# TODO: Find a better way to do this
117-
model.set_metadata_prop("bitfile_output", json.dumps(results))
127+
model.set_metadata_prop("bitfile", json.dumps(results))
128+
model.set_metadata_prop(
129+
"vivado_synth_rpt",
130+
json.dumps({device: Path(p).parent / "synth_report.xml" for device, p in results}),
131+
)
118132
return model, False
119133

120134

@@ -168,5 +182,4 @@ def apply(self, model: ModelWrapper) -> tuple[ModelWrapper, bool]: # noqa
168182
# Run the synthesis
169183
log.info("Starting synthesis...")
170184
model = model.transform(ParallelVitisSynthesis(self.cfg))
171-
172185
return model, False

src/finn/transformation/fpgadataflow/vitis_linking_configuration.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from finn.builder.build_dataflow_config import FpgaMemoryType, VitisOptStrategy
1919
from finn.templates import get_jinja_environment
20-
from finn.util.basic import make_build_dir
20+
from finn.util.basic import get_metadata_prop_path, make_build_dir
2121
from finn.util.exception import FINNInternalError, FINNUserError, FINNVitisLinkConfigError
2222
from finn.util.fpgadataflow import (
2323
check_all_sdp_nodes,
@@ -54,6 +54,7 @@ class VitisLinkConfiguration(DataClassYAMLMixin):
5454
optimization_level: str
5555
platform: str
5656
run_script_path: Path = field(init=False, default_factory=lambda: Path())
57+
gen_report_xml_path: Path = field(init=False, default_factory=lambda: Path())
5758
cu: list[str] = field(default_factory=list)
5859
nk: list[tuple[str, str]] = field(default_factory=list)
5960
sc: dict[str, list[str]] = field(default_factory=dict)
@@ -68,6 +69,7 @@ def __post_init__(self) -> None: # noqa
6869
self.config_path.parent.mkdir(exist_ok=True, parents=True)
6970
self.run_script_path = self.config_path.parent / "run_link.sh"
7071
self.run_script_path.parent.mkdir(exist_ok=True, parents=True)
72+
self.gen_report_xml_path = self.config_path.parent / "vitis_gen_report_xml.tcl"
7173

7274
def store(self, p: Path) -> None:
7375
"""Store the config as a YAML file, so that it can be loaded
@@ -94,18 +96,16 @@ def load_from_model(model: ModelWrapper) -> dict[int, VitisLinkConfiguration]:
9496
dict[int, VitisLinkConfiguration]: Maps device-IDs to their respective
9597
linking configurations.
9698
"""
97-
storage_path = model.get_metadata_prop("vitis_link_configs")
98-
if storage_path is None:
99-
raise FINNVitisLinkConfigError(
99+
storage_path = get_metadata_prop_path(
100+
model,
101+
"vitis_link_configs",
102+
must_exist=True,
103+
custom_error=(
100104
"Cannot load VitisLinkConfig from model, "
101105
"since the metadata prop "
102106
"'vitis_link_configs' was not found!"
103-
)
104-
storage_path = Path(storage_path)
105-
if not storage_path.exists():
106-
raise FINNVitisLinkConfigError(
107-
f"Cannot load VitisLinkConfigs from invalid path: {storage_path}"
108-
)
107+
),
108+
)
109109

110110
configs = {}
111111
for device_path in storage_path.iterdir():
@@ -469,6 +469,15 @@ def generate_config(self) -> None:
469469
)
470470
self.config_path.write_text(rendered)
471471

472+
def generate_report_tcl_script(self) -> None:
473+
"""Generate a tcl script to output a resource report as an XML. Stored
474+
in the link directory as "vitis_gen_report_xml.tcl".
475+
"""
476+
env = get_jinja_environment()
477+
template = env.get_template("vitis_link/vitis_gen_report_xml.tcl.jinja")
478+
rendered = template.render(VITIS_PROJ_PATH=str(self.config_path.parent.absolute()))
479+
self.gen_report_xml_path.write_text(rendered)
480+
472481
def generate_run_script(self) -> None:
473482
"""Generate a shell script to start v++ with the correct parameters.
474483
Produces the shell script next to the path of the config file
@@ -661,4 +670,5 @@ def apply(self, model: ModelWrapper) -> tuple[ModelWrapper, bool]: # noqa
661670
for config in configs.values():
662671
config.generate_config()
663672
config.generate_run_script()
673+
config.generate_report_tcl_script()
664674
return model, False

0 commit comments

Comments
 (0)