Skip to content

Commit fd91cd2

Browse files
committed
Starting working on Python script for automatic HLS project generation.
1 parent add6c88 commit fd91cd2

File tree

3 files changed

+251
-7
lines changed

3 files changed

+251
-7
lines changed

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ The approximation algorithms are in the `python/` folder.
1212

1313
* CMake
1414
* Xilinx Vivado 2018.3 (deprecated)
15-
* Xilinx Vitis 2021.1 (deprecated)
15+
* Xilinx Vitis 2021.1
1616

1717
### CMake Simulation
1818

19+
In order to make CMake include the HLS header libraries, one must modify the file `FindVitis.cmake` under `cmake/Modules`.
20+
1921
#### Windows
2022

21-
Simulation is working assuming the Xilinx OpenCV DLLs are copied into the `bin/` folder along side with the generated executables.
2223
```
2324
"C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat" amd64
2425
mkdir build
@@ -31,7 +32,7 @@ cmake --build . --config Release
3132
mkdir build
3233
cd build
3334
cmake ..
34-
make all
35+
make -j 4 all
3536
```
3637

3738
## Notes on Using Vitis HLS
@@ -50,7 +51,7 @@ Note: for using external DMAs, we need the TLAST, TKEEP and TSTRB signals. In pa
5051

5152
This repository contains a wrapper class for kernel arguments of type `hls::stream` named `AxiStreamInterface`. The class is implemented following a _Policy-based_ C++ paradigm, meaning that it accepts either a `AxiStreamPort` or `AxiStreamFifo` as possible policies (in practice, a template argument).
5253

53-
The idea is to have a kernel argument, i.e. an HLS port, which can be either an AXIS interface with side-channels, or a bare FIFO interface connected to another kernel. In fact, Vitis HLS doesn't allow stream interfaces with side-channels within an IP. To overcome the issue, the `AxiStreamInterface` can be customized to be an IP port or a FIFO port, depending on the use of the kernel.
54+
The idea is to have a kernel argument, i.e. an HLS port, which can be either an AXIS interface with side-channels, or a bare FIFO interface connected to another kernel. In fact, Vitis HLS doesn't allow stream interfaces with side-channels *within* an IP. To overcome this issue, the `AxiStreamInterface` can be customized to be an IP port or a FIFO port, depending on the use of the kernel.
5455

5556
An example of this can be seen in `HlsKernelU` and in `svd::SvdKernel`, which specialize the `svd::KernelU` function template. In the first case, the `svd::KernelU` has its output stream port `xu_port` connected to one of the IP's ports (with side-channels). In the latter case instead, `svd::KernelU` is connected to `svd::KernelS`, and so its `xu_port` argument is an internal FIFO (without side-channels).
5657

@@ -73,8 +74,8 @@ The type `ap_axiu` must now be used to generate AXIS with side channels.
7374
7475
### hls::vector Arrays on AXI-Lite Interfaces
7576
76-
In Vitis 2021.1 it **not** allowed to have `hls::vector` type arguments mapped to AXI-Lite interfaces.
77-
Instead, use a bare arrays, *e.g.* `const int x[N]` instead of `const hls::vector<int, N> x`.
77+
In Vitis 2021.1 it is **not** allowed to have `hls::vector` type arguments mapped to AXI-Lite interfaces.
78+
Instead, use bare arrays, *e.g.* `const int x[N]` instead of `const hls::vector<int, N> x`.
7879
7980
### Partitioning hls::vector Arrays
8081

include/layers/lstm/lstm_data_handler.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -668,7 +668,9 @@ class AcceleratorBlob {
668668
for (int i = 0; i < this->lstm_num_inputs_; ++i) {
669669
for (int j = 0; j < this->lstm_output_size_; ++j) {
670670
if (verbose > 0) {
671-
std:: cout << j << ") hls/emulator: " << this->fix_h_curr_[i][j] << " / " << x[i][j];
671+
auto h_tmp = this->fix_h_curr_[i][j];
672+
auto x_tmp = x[i][j];
673+
std:: cout << j << ") hls/emulator: " << h_tmp << " / " << x_tmp;
672674
}
673675
if (this->fix_h_curr_[i][j] != x[i][j]) {
674676
++num_errors;

make_hls.py

Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
import os
2+
import argparse
3+
import re
4+
5+
def get_tcl_utils():
6+
utils = '''#
7+
# @brief Greps a file content and writes matches to a file.
8+
#
9+
# @param re Regular expression
10+
# @param lines Number of lines to report/include after the found match
11+
# @param fin The fin pointer
12+
# @param fout The fout pointer
13+
#
14+
proc grep {re lines fin write_fout fout} {
15+
set cnt 0
16+
set match false
17+
seek $fin 0
18+
while {[gets $fin line] >= 0} {
19+
if [regexp -- $re $line] {
20+
set cnt 0
21+
set match true
22+
}
23+
if {$match && ($cnt < $lines)} {
24+
puts $line
25+
if {$write_fout} {
26+
puts $fout $line
27+
}
28+
set cnt [expr {$cnt +1}]
29+
} else {
30+
set match false
31+
}
32+
}
33+
}
34+
35+
'''
36+
return utils
37+
38+
def is_top_file(filename, top):
39+
found = False
40+
with open(filename, 'r') as f:
41+
for line in f:
42+
if top in line:
43+
found = True
44+
break
45+
return found
46+
47+
def get_dependent_files(filename):
48+
headers = []
49+
with open(filename, 'r') as f:
50+
for line in f:
51+
if '#include' in line:
52+
headers.append(line)
53+
return headers
54+
55+
def main():
56+
57+
avail_fpgas = {
58+
'ZedBoard' : 'xc7z020clg484-1',
59+
'ZCU104' : 'xczu7ev-ffvc1156-2-e',
60+
'ZCU102' : 'xczu9eg-ffvb1156-2-i',
61+
'Arty-7' : 'xc7a100tcsg324-1'
62+
}
63+
64+
parser = argparse.ArgumentParser(description='Generate a TCL script file for HLS projects generation.')
65+
parser.add_argument('top', metavar='top', type=str, help='The top HLS function name.')
66+
parser.add_argument('--top_file', type=str, default='', help='The filename of the top HLS function. Default: None.')
67+
68+
parser.add_argument('--script_name', type=str, default='run_hls_test.tcl', help='Generated TCL filename. Default: run_hls.tcl.')
69+
parser.add_argument('--run_hls', action='store_true', help='Run th generated TCL file. Default: False.')
70+
71+
parser.add_argument('--board', type=str, choices=avail_fpgas.keys(), default='ZedBoard', help='The target FPGA board.')
72+
parser.add_argument('--period', type=str, default='10', help='Clock period in ns. Default: 10.')
73+
# Actions
74+
parser.add_argument('--use_vivado_hls', action='store_true', help='Use Vivado HLS. Default is Vitis HLS.')
75+
parser.add_argument('--no_synthesis', action='store_true', help='Do not run synthesis.')
76+
parser.add_argument('--csim', action='store_true', help='Run C/C++ simulation.')
77+
parser.add_argument('--cosim', action='store_true', help='Run C/C++ co-simulation.')
78+
parser.add_argument('--export', action='store_true', help='Export IP.')
79+
parser.add_argument('--place_and_route', action='store_true', help='Export IP and check place-and-route.')
80+
parser.add_argument('--no_reset_prj', action='store_true', help='Do not reset HLS project.')
81+
parser.add_argument('--debug_dataflow', action='store_true', help='Resize FIFO depths for dataflow check.')
82+
parser.add_argument('--profile_dataflow', action='store_true', help='Resize FIFO depths for dataflow check.')
83+
84+
parser.add_argument('--resize_fifos', action='store_true', help='Resize FIFO depths for dataflow check.')
85+
parser.add_argument('--scheduler_effort', type=str, choices=['low', 'medium', 'high'], default='high', help='The HLS scheduler effort. Default: high.')
86+
87+
parser.add_argument('--tb_dir', type=str, default='testbenches', help='Testbench dir name under src files. Default: testbenches.')
88+
parser.add_argument('--tb_file', type=str, default='', help='Testbench file name under tb_dir. Default: None.')
89+
parser.add_argument('--cc', type=str, choices=['11', '14'], default='14', help='The target C++ version. Default: C++14.')
90+
parser.add_argument('--cflags', type=str, default='-O3 -std=c++14', help='C++ cflags. Default: -O3 -std=c++14.')
91+
parser.add_argument('--ldflags', type=str, default='', help='C++ linker flags. Default: None.')
92+
parser.add_argument('--argv', type=str, default='', help='The simulation and co-simulation arguments.')
93+
parser.add_argument('--src', type=str, default='src', help='The C/C++ source directory. Default: src.')
94+
parser.add_argument('--include', type=str, default='include', help='The C/C++ include directory. Default: include.')
95+
parser.add_argument('--defines', type=str, default='', help='The C/C++ defines. Default: None.')
96+
parser.add_argument('--defines_file', type=str, default='', help='The file containing all C/C++ defines. Default: None.')
97+
98+
args = parser.parse_args()
99+
100+
if args.csim and args.tb_file == '' or args.cosim and args.tb_file == '':
101+
parser.error('The --csim and --cosim arguments requires --tb_file.')
102+
103+
print(args)
104+
105+
curr_dir = os.getcwd().replace('\\', '/') + '/'
106+
hls_prj_dir = curr_dir + '/hls_prj/'
107+
hls_report_dir = hls_prj_dir + '/reports'
108+
hls_tool = 'vhls' if args.use_vivado_hls else 'vitis'
109+
prj_name = f'{hls_tool}_{args.board}_{args.top}'
110+
111+
cflags = args.cflags + ' -I' + curr_dir + args.include
112+
if hls_tool == 'vhls':
113+
cflags = '-fno-builtin ' + cflags.replace('c++14', 'c++0x')
114+
print(f'[WARNING] Replacing C++14 with C++11 in Vivado HLS. CFLAGS: {cflags}')
115+
116+
with open(args.script_name, 'w') as f:
117+
f.write(get_tcl_utils())
118+
f.write(f'exec mkdir -p -- {hls_prj_dir}\n')
119+
f.write(f'exec mkdir -p -- {hls_report_dir}\n')
120+
f.write(f'cd {hls_prj_dir}\n')
121+
f.write('open_project {} "{}"\n'.format('-reset' if not args.no_reset_prj else '', prj_name))
122+
f.write(f'set_top "{args.top}"\n')
123+
if not args.no_reset_prj:
124+
pass
125+
if args.csim or args.cosim:
126+
pass
127+
if hls_tool == 'vitis':
128+
f.write(f'open_solution -flow_target vivado -reset "{args.top}"\n')
129+
else:
130+
f.write(f'open_solution "{args.top}"\n')
131+
if not args.no_reset_prj:
132+
f.write(f'set_part {avail_fpgas[args.board]} ;# {args.board}\n')
133+
f.write(f'create_clock -period {args.period} -name default\n')
134+
if hls_tool == 'vitis':
135+
f.write(f'config_compile -name_max_length=12 -pipeline_style=frp -enable_auto_rewind=1\n')
136+
else:
137+
f.write(f'config_compile -name_max_length=12\n')
138+
f.write(f'config_schedule -effort={args.scheduler_effort} -relax_ii_for_timing=0\n')
139+
f.write(f'config_core DSP48 -latency 3\n')
140+
141+
# TODO: Recursively include only the files from which the top function depends on.
142+
headers = []
143+
if args.top_file:
144+
for fpath, subdirs, files in os.walk(args.src):
145+
for fname in files:
146+
if args.top_file.replace('.cpp', '') == fname.replace('.cpp', '') or \
147+
args.top_file.replace('.h', '') == fname.replace('.h', ''):
148+
headers_decl = get_dependent_files(fpath + '/' + fname)
149+
for h in headers_decl:
150+
match = re.search('"(.*?)"', h)
151+
if match is not None:
152+
for m in match.groups():
153+
headers.append(m.replace('"', ''))
154+
match = re.search('<(.*?)>', h)
155+
if match is not None:
156+
for m in match.groups():
157+
tmp = m.replace('<', '')
158+
tmp = tmp.replace('>', '')
159+
headers.append(tmp)
160+
print(headers)
161+
162+
f.write(f'# Source files\n')
163+
for fpath, subdirs, files in os.walk(args.src):
164+
for fname in files:
165+
unix_filename = curr_dir + '/' + fpath.replace('\\', '/') + '/' + fname
166+
if fname.endswith('.cpp') or fname.endswith('.cc'):
167+
if args.tb_dir.replace('\\', '/') not in fpath:
168+
print(f'[INFO] Adding synthesis file: {unix_filename}')
169+
f.write(f'add_files {unix_filename} -cflags "{cflags}"\n')
170+
else:
171+
if fname.startswith(args.tb_file):
172+
print(f'[INFO] Adding simulation file: {unix_filename}')
173+
f.write(f'add_files -tb {unix_filename} -cflags "{cflags}"\n')
174+
f.write(f'# Include files\n')
175+
for fpath, subdirs, files in os.walk(args.include):
176+
for fname in files:
177+
unix_filename = curr_dir + '/' + fpath.replace('\\', '/') + '/' + fname
178+
if fname.endswith('.h'):
179+
if args.tb_dir.replace('\\', '/') not in fpath:
180+
print(f'[INFO] Adding synthesis file: {unix_filename}')
181+
f.write(f'add_files {unix_filename} -cflags "{cflags}"\n')
182+
else:
183+
if fname.startswith(args.tb_file):
184+
print(f'[INFO] Adding simulation file: {unix_filename}')
185+
f.write(f'add_files -tb {unix_filename} -cflags "{cflags}"\n')
186+
187+
if args.csim:
188+
f.write(f'csim_design -clean -O -ldflags {args.ldflags} -argv {args.argv}\n')
189+
if not args.no_synthesis:
190+
f.write(f'csynth_design\n')
191+
csynth_report = hls_prj_dir + prj_name + f'/solution_{args.top}/syn/report/{args.top}_csynth.rpt'
192+
report_outfile = hls_report_dir + f'/{hls_tool}_{args.board}_{args.top}.rpt'
193+
report_info = f'''puts "================================================================"
194+
puts "\[INFO\] Reporting information"
195+
puts "================================================================"
196+
set fin [open {csynth_report} r]
197+
set fout [open {report_outfile} a]
198+
grep "== Performance Estimates" 18 $fin 0 $fout
199+
grep "== Utilization Estimates" 20 $fin 0 $fout
200+
close $fin
201+
close $fout'''
202+
f.write(f'{report_info}\n')
203+
204+
if args.cosim:
205+
cosim_cmd = f'cosim_design -trace_level port -ldflags "{args.ldflags}" -argv "{args.argv}"'
206+
if hls_tool == 'vitis' and args.debug_dataflow:
207+
cosim_cmd += ' -enable_dataflow_profiling=1 -enable_fifo_sizing=1'
208+
209+
210+
f.write(f'{cosim_cmd}\n')
211+
212+
cosim_report = hls_prj_dir + prj_name + f'/solution_{args.top}/syn/report/{args.top}_cosim.rpt'
213+
report_info = f'''puts "================================================================"
214+
puts "\[INFO\] Reporting information"
215+
puts "================================================================"
216+
set fin [open {csynth_report} r]
217+
set fout [open {report_outfile} a]
218+
grep "Simulation tool" 10 $fin 0 $fout
219+
close $fin
220+
close $fout'''
221+
f.write(f'{report_info}\n')
222+
if args.export:
223+
f.write('export_design -format ip_catalog\n')
224+
if args.place_and_route:
225+
f.write('export_design -flow impl -rtl verilog -format ip_catalog\n')
226+
227+
closing = f'''puts "================================================================"
228+
puts "\[INFO\] Closing project: {prj_name}"
229+
puts "================================================================"
230+
exit
231+
cd {curr_dir}'''
232+
f.write(f'{closing}\n')
233+
234+
if args.run_hls:
235+
if hls_tool == 'vhls':
236+
os.system(f'vivado_hls -f {args.script_name}')
237+
else:
238+
os.system(f'D:/Programs/Xilinx/Vitis_HLS/2021.1/bin/vitis_hls -f {args.script_name}')
239+
240+
if __name__ == '__main__':
241+
main()

0 commit comments

Comments
 (0)