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