Skip to content

Commit c1415b4

Browse files
Merge C++ driver
2 parents 7edf8d5 + 808798a commit c1415b4

10 files changed

Lines changed: 632 additions & 313 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ __pycache__/*
4040
.cache/*
4141
.*.swp
4242
*.ipynb_checkpoints*
43+
*.sif
4344

4445
# Project files
4546
.vscode

.gitmodules

Whitespace-only changes.

src/finn/builder/build_dataflow.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,15 @@ def build_dataflow_cfg(model_filename, cfg: DataflowBuildConfig):
153153
logging.captureWarnings(True)
154154

155155
log = logging.getLogger("build_dataflow")
156+
156157
# mirror stdout and stderr to log
157158
sys.stdout = PrintLogger(log, logging.INFO, sys.stdout)
158159
sys.stderr = PrintLogger(log, logging.ERROR, sys.stderr)
160+
console = Console(file=sys.stdout.console)
159161

160162
if cfg.console_log_level != "NONE":
161163
# set up console logger
162-
console = RichHandler(show_time=False, show_path=False)
164+
console = RichHandler(show_time=False, show_path=False, console=console)
163165

164166
if cfg.console_log_level == "DEBUG":
165167
console.setLevel(logging.DEBUG)
@@ -198,7 +200,6 @@ def build_dataflow_cfg(model_filename, cfg: DataflowBuildConfig):
198200
except: # noqa
199201
# print exception info and traceback
200202
extype, value, tb = sys.exc_info()
201-
console = Console()
202203
console.print_exception(show_locals=False)
203204
# start postmortem debug if configured
204205
if cfg.enable_build_pdb_debug:

src/finn/builder/build_dataflow_config.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ class DataflowOutputType(str, Enum):
7373
RTLSIM_PERFORMANCE = "rtlsim_performance"
7474
BITFILE = "bitfile"
7575
PYNQ_DRIVER = "pynq_driver"
76+
CPP_DRIVER = "cpp_driver"
7677
DEPLOYMENT_PACKAGE = "deployment_package"
7778

7879

@@ -141,7 +142,7 @@ class VerificationStepType(str, Enum):
141142
"step_measure_rtlsim_performance",
142143
"step_out_of_context_synthesis",
143144
"step_synthesize_bitfile",
144-
"step_make_pynq_driver",
145+
"step_make_driver",
145146
"step_deployment_package",
146147
]
147148

@@ -382,6 +383,11 @@ class DataflowBuildConfig(DataClassJSONMixin, DataClassYAMLMixin):
382383
#: rtlsim, otherwise they will be replaced by RTL implementations.
383384
rtlsim_use_vivado_comps: Optional[bool] = True
384385

386+
#: Determine if the C++ driver should be generated instead of the PYNQ driver
387+
#: If set to latest newest version will be used
388+
#: If set to commit hash specified version will be used
389+
cpp_driver_version: Optional[str] = "latest"
390+
385391
def _resolve_hls_clk_period(self):
386392
if self.hls_clk_period_ns is None:
387393
# use same clk for synth and hls if not explicitly specified

src/finn/builder/build_dataflow_steps.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import numpy as np
3232
import os
3333
import shutil
34+
import warnings
3435
from copy import deepcopy
3536
from functools import partial
3637
from qonnx.core.modelwrapper import ModelWrapper
@@ -79,7 +80,7 @@
7980
from finn.transformation.fpgadataflow.hlssynth_ip import HLSSynthIP
8081
from finn.transformation.fpgadataflow.insert_dwc import InsertDWC
8182
from finn.transformation.fpgadataflow.insert_fifo import InsertFIFO
82-
from finn.transformation.fpgadataflow.make_pynq_driver import MakePYNQDriver
83+
from finn.transformation.fpgadataflow.make_driver import MakeCPPDriver, MakePYNQDriver
8384
from finn.transformation.fpgadataflow.make_zynq_proj import ZynqBuild
8485
from finn.transformation.fpgadataflow.minimize_accumulator_width import MinimizeAccumulatorWidth
8586
from finn.transformation.fpgadataflow.minimize_weight_bit_width import MinimizeWeightBitWidth
@@ -710,15 +711,33 @@ def step_measure_rtlsim_performance(model: ModelWrapper, cfg: DataflowBuildConfi
710711
return model
711712

712713

713-
def step_make_pynq_driver(model: ModelWrapper, cfg: DataflowBuildConfig):
714-
"""Create a PYNQ Python driver that can be used to interface the generated
715-
accelerator."""
714+
def step_make_driver(model: ModelWrapper, cfg: DataflowBuildConfig):
715+
"""Create a driver that can be used to interface the generated accelerator.
716+
Use DataflowBuildConfig to select PYNQ Python or C++ driver."""
716717

718+
driver_dir = os.path.join(cfg.output_dir, "driver")
717719
if DataflowOutputType.PYNQ_DRIVER in cfg.generate_outputs:
718-
driver_dir = cfg.output_dir + "/driver"
720+
# generate PYNQ driver
719721
model = model.transform(MakePYNQDriver(cfg._resolve_driver_platform()))
720722
shutil.copytree(model.get_metadata_prop("pynq_driver_dir"), driver_dir, dirs_exist_ok=True)
721-
log.info(f"PYNQ Python driver written into {driver_dir}")
723+
log.info("PYNQ Python driver written into " + driver_dir)
724+
elif DataflowOutputType.CPP_DRIVER in cfg.generate_outputs:
725+
# generate C++ Driver
726+
727+
model = model.transform(
728+
MakeCPPDriver(
729+
cfg._resolve_driver_platform(),
730+
build_dir=cfg.output_dir,
731+
version=cfg.cpp_driver_version,
732+
driver_dir=driver_dir,
733+
)
734+
)
735+
log.info("C++ driver written into " + driver_dir)
736+
else:
737+
warnings.warn(
738+
"The step step_make_driver is in the build list but will not be executed"
739+
+ " since no driver is selected in generate_outputs in your build.py file!"
740+
)
722741
return model
723742

724743

@@ -841,7 +860,7 @@ def step_deployment_package(model: ModelWrapper, cfg: DataflowBuildConfig):
841860
"step_set_fifo_depths": step_set_fifo_depths,
842861
"step_create_stitched_ip": step_create_stitched_ip,
843862
"step_measure_rtlsim_performance": step_measure_rtlsim_performance,
844-
"step_make_pynq_driver": step_make_pynq_driver,
863+
"step_make_driver": step_make_driver,
845864
"step_out_of_context_synthesis": step_out_of_context_synthesis,
846865
"step_synthesize_bitfile": step_synthesize_bitfile,
847866
"step_deployment_package": step_deployment_package,
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# Copyright (C) 2025, Advanced Micro Devices, Inc.
2+
# All rights reserved.
3+
4+
# Redistribution and use in source and binary forms, with or without
5+
# modification, are permitted provided that the following conditions are met:
6+
7+
# * Redistributions of source code must retain the above copyright notice, this
8+
# list of conditions and the following disclaimer.
9+
10+
# * Redistributions in binary form must reproduce the above copyright notice,
11+
# this list of conditions and the following disclaimer in the documentation
12+
# and/or other materials provided with the distribution.
13+
14+
# * Neither the name of FINN nor the names of its
15+
# contributors may be used to endorse or promote products derived from
16+
# this software without specific prior written permission.
17+
18+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21+
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
22+
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23+
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
24+
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
25+
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
26+
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27+
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28+
29+
import numpy as np
30+
from qonnx.core.modelwrapper import ModelWrapper
31+
from qonnx.custom_op.registry import getCustomOp
32+
from qonnx.util.basic import gen_finn_dt_tensor, roundup_to_integer_multiple
33+
from typing import Dict
34+
35+
import finn.util.data_packing as dpk
36+
from finn.util.data_packing import (
37+
hexstring2npbytearray,
38+
pack_innermost_dim_as_hex_string,
39+
)
40+
41+
42+
def to_external_tensor(init, w_dtype):
43+
"""Return an appropriately formatted and packed numpy byte array for given
44+
external parameter tensor."""
45+
46+
weight_width = init.shape[1] * w_dtype.bitwidth()
47+
weight_width_padded = roundup_to_integer_multiple(weight_width, 4)
48+
hex_init = pack_innermost_dim_as_hex_string(init, w_dtype, weight_width_padded, prefix="0x")
49+
ext_weight = np.array([], dtype=np.uint8)
50+
for line in hex_init:
51+
array_line = [x for x in reversed(hexstring2npbytearray(line, remove_prefix="0x"))]
52+
ext_weight = np.append(ext_weight, array_line)
53+
54+
return ext_weight
55+
56+
57+
def get_driver_shapes(model: ModelWrapper) -> Dict:
58+
idt = []
59+
idma_names = []
60+
ishape_normal = []
61+
ishape_folded = []
62+
ishape_packed = []
63+
for idma_ind, graph_in in enumerate(model.graph.input):
64+
i_tensor_name = graph_in.name
65+
# get inp tensor properties
66+
i_tensor_dt = model.get_tensor_datatype(i_tensor_name)
67+
i_tensor_shape_normal = tuple(model.get_tensor_shape(i_tensor_name))
68+
# go down into dataflow partition to get folded shape info etc
69+
# TODO consider setting these as attributes during dataflow partitioning
70+
i_consumer = model.find_consumer(i_tensor_name)
71+
assert (
72+
i_consumer.op_type == "StreamingDataflowPartition"
73+
), """
74+
Ensure CreateDataflowPartition called before driver creation."""
75+
first_df_model = ModelWrapper(getCustomOp(i_consumer).get_nodeattr("model"))
76+
assert (
77+
first_df_model.graph.node[0].op_type == "IODMA_hls"
78+
), "First partition must hold input IODMA"
79+
successors = model.find_direct_successors(i_consumer)
80+
successor_input_num = list(successors[0].input).index(i_consumer.output[0])
81+
successor_sdp = getCustomOp(successors[0])
82+
successor_df_model = ModelWrapper(successor_sdp.get_nodeattr("model"))
83+
first_node = successor_df_model.find_consumer(
84+
successor_df_model.graph.input[successor_input_num].name
85+
)
86+
i_tensor_shape_folded = tuple(getCustomOp(first_node).get_folded_input_shape())
87+
# generate dummy folded i/o tensors and their packed versions
88+
i_tensor_dummy_folded = gen_finn_dt_tensor(i_tensor_dt, i_tensor_shape_folded)
89+
i_tensor_dummy_packed = dpk.finnpy_to_packed_bytearray(i_tensor_dummy_folded, i_tensor_dt)
90+
i_tensor_shape_packed = i_tensor_dummy_packed.shape
91+
# append all input tensor info to relevant lists
92+
idt.append("DataType['%s']" % i_tensor_dt.name)
93+
ishape_normal.append(i_tensor_shape_normal)
94+
ishape_folded.append(i_tensor_shape_folded)
95+
ishape_packed.append(i_tensor_shape_packed)
96+
idma_names.append(getCustomOp(i_consumer).get_nodeattr("instance_name"))
97+
98+
odt = []
99+
odma_names = []
100+
oshape_normal = []
101+
oshape_folded = []
102+
oshape_packed = []
103+
for odma_ind, graph_out in enumerate(model.graph.output):
104+
o_tensor_name = graph_out.name
105+
# get inp tensor properties
106+
o_tensor_dt = model.get_tensor_datatype(o_tensor_name)
107+
o_tensor_shape_normal = tuple(model.get_tensor_shape(o_tensor_name))
108+
# go down into IODMA partition to get folded shape info etc
109+
# TODO consider setting these as attributes during dataflow partitioning
110+
o_producer = model.find_producer(o_tensor_name)
111+
assert (
112+
o_producer.op_type == "StreamingDataflowPartition"
113+
), """
114+
Ensure CreateDataflowPartition called before driver creation."""
115+
df_model = ModelWrapper(getCustomOp(o_producer).get_nodeattr("model"))
116+
assert df_model.graph.node[-1].op_type == "IODMA_hls", "Partition must hold output IODMA"
117+
predecessors = model.find_direct_predecessors(o_producer)
118+
predecessor_output_num = list(predecessors[0].output).index(o_producer.input[0])
119+
predecessor_sdp = getCustomOp(predecessors[0])
120+
predecessor_df_model = ModelWrapper(predecessor_sdp.get_nodeattr("model"))
121+
last_node = predecessor_df_model.find_producer(
122+
predecessor_df_model.graph.output[predecessor_output_num].name
123+
)
124+
o_tensor_shape_folded = tuple(getCustomOp(last_node).get_folded_output_shape())
125+
o_tensor_dummy_folded = gen_finn_dt_tensor(o_tensor_dt, o_tensor_shape_folded)
126+
o_tensor_dummy_packed = dpk.finnpy_to_packed_bytearray(o_tensor_dummy_folded, o_tensor_dt)
127+
o_tensor_shape_packed = o_tensor_dummy_packed.shape
128+
# append all output tensor info to relevant lists
129+
odt.append("DataType['%s']" % o_tensor_dt.name)
130+
oshape_normal.append(o_tensor_shape_normal)
131+
oshape_folded.append(o_tensor_shape_folded)
132+
oshape_packed.append(o_tensor_shape_packed)
133+
odma_names.append(getCustomOp(o_producer).get_nodeattr("instance_name"))
134+
135+
return {
136+
"idt": idt,
137+
"idma_names": idma_names,
138+
"ishape_normal": ishape_normal,
139+
"ishape_folded": ishape_folded,
140+
"ishape_packed": ishape_packed,
141+
"odt": odt,
142+
"odma_names": odma_names,
143+
"oshape_normal": oshape_normal,
144+
"oshape_folded": oshape_folded,
145+
"oshape_packed": oshape_packed,
146+
}

0 commit comments

Comments
 (0)