-
Notifications
You must be signed in to change notification settings - Fork 247
/
Copy pathbasic.py
308 lines (259 loc) · 11.4 KB
/
basic.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
# Copyright (C) 2024, Advanced Micro Devices, Inc.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of FINN nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import os
import subprocess
import sys
import tempfile
from qonnx.util.basic import roundup_to_integer_multiple
# test boards
test_board_map = ["Pynq-Z1", "KV260_SOM", "ZCU104", "U250"]
# mapping from PYNQ board names to FPGA part names
pynq_part_map = dict()
pynq_part_map["Ultra96"] = "xczu3eg-sbva484-1-e"
pynq_part_map["Ultra96-V2"] = "xczu3eg-sbva484-1-i"
pynq_part_map["Pynq-Z1"] = "xc7z020clg400-1"
pynq_part_map["Pynq-Z2"] = "xc7z020clg400-1"
pynq_part_map["ZCU102"] = "xczu9eg-ffvb1156-2-e"
pynq_part_map["ZCU104"] = "xczu7ev-ffvc1156-2-e"
pynq_part_map["ZCU111"] = "xczu28dr-ffvg1517-2-e"
pynq_part_map["RFSoC2x2"] = "xczu28dr-ffvg1517-2-e"
pynq_part_map["RFSoC4x2"] = "xczu48dr-ffvg1517-2-e"
pynq_part_map["KV260_SOM"] = "xck26-sfvc784-2LV-c"
# native AXI HP port width (in bits) for PYNQ boards
pynq_native_port_width = dict()
pynq_native_port_width["Pynq-Z1"] = 64
pynq_native_port_width["Pynq-Z2"] = 64
pynq_native_port_width["Ultra96"] = 128
pynq_native_port_width["Ultra96-V2"] = 128
pynq_native_port_width["ZCU102"] = 128
pynq_native_port_width["ZCU104"] = 128
pynq_native_port_width["ZCU111"] = 128
pynq_native_port_width["RFSoC2x2"] = 128
pynq_native_port_width["RFSoC4x2"] = 128
pynq_native_port_width["KV260_SOM"] = 128
# Alveo device and platform mappings
alveo_part_map = dict()
alveo_part_map["U50"] = "xcu50-fsvh2104-2L-e"
alveo_part_map["U200"] = "xcu200-fsgd2104-2-e"
alveo_part_map["U250"] = "xcu250-figd2104-2L-e"
alveo_part_map["U280"] = "xcu280-fsvh2892-2L-e"
alveo_part_map["U55C"] = "xcu55c-fsvh2892-2L-e"
alveo_default_platform = dict()
alveo_default_platform["U50"] = "xilinx_u50_gen3x16_xdma_5_202210_1"
alveo_default_platform["U200"] = "xilinx_u200_gen3x16_xdma_2_202110_1"
alveo_default_platform["U250"] = "xilinx_u250_gen3x16_xdma_2_1_202010_1"
alveo_default_platform["U280"] = "xilinx_u280_gen3x16_xdma_1_202211_1"
alveo_default_platform["U55C"] = "xilinx_u55c_gen3x16_xdma_3_202210_1"
# Create a joint part map, encompassing other boards too
part_map = {**pynq_part_map, **alveo_part_map}
part_map["VEK280"] = "xcve2802-vsvh1760-2MP-e-S"
part_map["VCK190"] = "xcvc1902-vsva2197-2MP-e-S"
part_map["V80"] = "xcv80-lsva4737-2MHP-e-s"
def get_rtlsim_trace_depth():
"""Return the trace depth for rtlsim via PyVerilator. Controllable
via the RTLSIM_TRACE_DEPTH environment variable. If the env.var. is
undefined, the default value of 1 is returned. A trace depth of 1
will only show top-level signals and yield smaller .vcd files.
The following depth values are of interest for whole-network stitched IP
rtlsim:
- level 1 shows top-level input/output streams
- level 2 shows per-layer input/output streams
- level 3 shows per full-layer I/O including FIFO count signals
"""
try:
return int(os.environ["RTLSIM_TRACE_DEPTH"])
except KeyError:
return 1
def get_remote_vivado():
"""Return the address of the remote Vivado synthesis server as set by the,
REMOTE_VIVADO environment variable, otherwise return None"""
try:
return os.environ["REMOTE_VIVADO"]
except KeyError:
return None
def get_finn_root():
"Return the root directory that FINN is cloned into."
try:
return os.environ["FINN_ROOT"]
except KeyError:
raise Exception(
"""Environment variable FINN_ROOT must be set
correctly. Please ensure you have launched the Docker contaier correctly.
"""
)
def pyverilate_get_liveness_threshold_cycles():
"""Return the number of no-output cycles rtlsim will wait before assuming
the simulation is not finishing and throwing an exception."""
return int(os.getenv("LIVENESS_THRESHOLD", 10000))
def make_build_dir(prefix=""):
"""Creates a folder with given prefix to be used as a build dir.
Use this function instead of tempfile.mkdtemp to ensure any generated files
will survive on the host after the FINN Docker container exits."""
try:
tmpdir = tempfile.mkdtemp(prefix=prefix)
newdir = tmpdir.replace("/tmp", os.environ["FINN_BUILD_DIR"])
os.makedirs(newdir)
return newdir
except KeyError:
raise Exception(
"""Environment variable FINN_BUILD_DIR must be set
correctly. Please ensure you have launched the Docker contaier correctly.
"""
)
class CppBuilder:
"""Builds the g++ compiler command to produces the executable of the c++ code
in code_gen_dir which is passed to the function build() of this class."""
def __init__(self):
self.include_paths = []
self.cpp_files = []
self.executable_path = ""
self.code_gen_dir = ""
self.compile_components = []
self.compile_script = ""
def append_includes(self, library_path):
"""Adds given library path to include_paths list."""
self.include_paths.append(library_path)
def append_sources(self, cpp_file):
"""Adds given c++ file to cpp_files list."""
self.cpp_files.append(cpp_file)
def set_executable_path(self, path):
"""Sets member variable "executable_path" to given path."""
self.executable_path = path
def build(self, code_gen_dir):
"""Builds the g++ compiler command according to entries in include_paths
and cpp_files lists. Saves it in bash script in given folder and
executes it."""
# raise error if includes are empty
self.code_gen_dir = code_gen_dir
self.compile_components.append("g++ -o " + str(self.executable_path))
for cpp_file in self.cpp_files:
self.compile_components.append(cpp_file)
for lib in self.include_paths:
self.compile_components.append(lib)
bash_compile = ""
for component in self.compile_components:
bash_compile += str(component) + " "
self.compile_script = str(self.code_gen_dir) + "/compile.sh"
with open(self.compile_script, "w") as f:
f.write("#!/bin/bash \n")
f.write(bash_compile + "\n")
bash_command = ["bash", self.compile_script]
process_compile = subprocess.Popen(bash_command, stdout=subprocess.PIPE)
process_compile.communicate()
def launch_process_helper(args, proc_env=None, cwd=None):
"""Helper function to launch a process in a way that facilitates logging
stdout/stderr with Python loggers.
Returns (cmd_out, cmd_err)."""
if proc_env is None:
proc_env = os.environ.copy()
with subprocess.Popen(
args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=proc_env, cwd=cwd
) as proc:
(cmd_out, cmd_err) = proc.communicate()
if cmd_out is not None:
cmd_out = cmd_out.decode("utf-8")
sys.stdout.write(cmd_out)
if cmd_err is not None:
cmd_err = cmd_err.decode("utf-8")
sys.stderr.write(cmd_err)
return (cmd_out, cmd_err)
def which(program):
"Python equivalent of the shell cmd 'which'."
# source:
# https://stackoverflow.com/questions/377017/test-if-executable-exists-in-python
def is_exe(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
fpath, fname = os.path.split(program)
if fpath:
if is_exe(program):
return program
else:
for path in os.environ["PATH"].split(os.pathsep):
exe_file = os.path.join(path, program)
if is_exe(exe_file):
return exe_file
return None
mem_primitives_versal = {
"URAM_72x4096": (72, 4096),
"URAM_36x8192": (36, 8192),
"URAM_18x16384": (18, 16384),
"URAM_9x32768": (9, 32768),
"BRAM18_36x512": (36, 512),
"BRAM18_18x1024": (18, 1024),
"BRAM18_9x2048": (9, 2048),
"LUTRAM": (1, 64),
}
def get_memutil_alternatives(
req_mem_spec, mem_primitives=mem_primitives_versal, sort_min_waste=True
):
"""Computes how many instances of a memory primitive are necessary to
implement a desired memory size, where req_mem_spec is the desired
size and the primitive_spec is the primitve size. The sizes are expressed
as tuples of (mem_width, mem_depth). Returns a list of tuples of the form
(primitive_name, (primitive_count, efficiency, waste)) where efficiency in
range [0,1] indicates how much of the total capacity is utilized, and waste
indicates how many bits of storage are wasted. If sort_min_waste is True,
the list is sorted by increasing waste.
"""
ret = [
(primitive_name, memutil(req_mem_spec, primitive_spec))
for (primitive_name, primitive_spec) in mem_primitives.items()
]
if sort_min_waste:
ret = sorted(ret, key=lambda x: x[1][2])
return ret
def memutil(req_mem_spec, primitive_spec):
"""Computes how many instances of a memory primitive are necessary to
implemented a desired memory size, where req_mem_spec is the desired
size and the primitive_spec is the primitve size. The sizes are expressed
as tuples of (mem_width, mem_depth). Returns (primitive_count, efficiency, waste)
where efficiency in range [0,1] indicates how much of the total capacity is
utilized, and waste indicates how many bits of storage are wasted."""
req_width, req_depth = req_mem_spec
prim_width, prim_depth = primitive_spec
match_width = roundup_to_integer_multiple(req_width, prim_width)
match_depth = roundup_to_integer_multiple(req_depth, prim_depth)
count_width = match_width // prim_width
count_depth = match_depth // prim_depth
count = count_depth * count_width
eff = (req_width * req_depth) / (count * prim_width * prim_depth)
waste = (count * prim_width * prim_depth) - (req_width * req_depth)
return (count, eff, waste)
def is_versal(fpgapart):
"""Returns whether board is part of the Versal family"""
return fpgapart[0:4] in ["xcvc", "xcve", "xcvp", "xcvm", "xqvc", "xqvm"] or fpgapart[0:5] in [
"xqrvc",
"xcv80",
]
def get_dsp_block(fpgapart):
if is_versal(fpgapart):
return "DSP58"
elif fpgapart[2] == "7":
return "DSP48E1"
else:
return "DSP48E2"