Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# -------------------------------------------------------------------------------------------------- 

2# Copyright (c) Lukas Vik. All rights reserved. 

3# 

4# This file is part of the tsfpga project. 

5# https://tsfpga.com 

6# https://gitlab.com/tsfpga/tsfpga 

7# -------------------------------------------------------------------------------------------------- 

8 

9from tsfpga.system_utils import create_file 

10from .common import to_tcl_path 

11 

12 

13class VivadoTcl: 

14 """ 

15 Class with methods for translating a set of sources into Vivado TCL 

16 """ 

17 

18 def __init__( 

19 self, 

20 name, 

21 ): 

22 self.name = name 

23 

24 # pylint: disable=too-many-arguments 

25 def create( 

26 self, 

27 project_folder, 

28 modules, 

29 part, 

30 top, 

31 run_index, 

32 generics=None, 

33 constraints=None, 

34 tcl_sources=None, 

35 build_step_hooks=None, 

36 ip_cache_path=None, 

37 disable_io_buffers=True, 

38 # Add no sources other than IP cores 

39 ip_cores_only=False, 

40 # Will be passed on to module functions. Enables parameterization of e.g. IP cores. 

41 other_arguments=None, 

42 ): 

43 generics = dict() if generics is None else generics 

44 other_arguments = dict() if other_arguments is None else other_arguments 

45 

46 tcl = f"create_project {self.name} {{{to_tcl_path(project_folder)}}} -part {part}\n" 

47 tcl += "set_property target_language VHDL [current_project]\n" 

48 

49 if ip_cache_path is not None: 

50 tcl += f"config_ip_cache -use_cache_location {{{to_tcl_path(ip_cache_path)}}}\n" 

51 tcl += "\n" 

52 

53 tcl += self._add_tcl_sources(tcl_sources) 

54 tcl += "\n" 

55 

56 if not ip_cores_only: 

57 tcl += self._add_module_source_files(modules=modules, other_arguments=other_arguments) 

58 tcl += "\n" 

59 tcl += self._add_generics(generics) 

60 tcl += "\n" 

61 tcl += self._add_constraints( 

62 self._iterate_constraints( 

63 modules=modules, constraints=constraints, other_arguments=other_arguments 

64 ) 

65 ) 

66 tcl += "\n" 

67 tcl += self._add_build_step_hooks(build_step_hooks, project_folder) 

68 tcl += "\n" 

69 

70 tcl += self._add_ip_cores(modules=modules, other_arguments=other_arguments) 

71 tcl += "\n" 

72 tcl += self._add_project_settings() 

73 tcl += "\n" 

74 tcl += f"current_run [get_runs synth_{run_index}]\n" 

75 tcl += "\n" 

76 tcl += f"set_property top {top} [current_fileset]\n" 

77 tcl += "reorder_files -auto -disable_unused\n" 

78 tcl += "\n" 

79 

80 if disable_io_buffers: 

81 tcl += ( 

82 "set_property -name {STEPS.SYNTH_DESIGN.ARGS.MORE OPTIONS} " 

83 f"-value -no_iobuf -objects [get_runs synth_{run_index}]" 

84 ) 

85 tcl += "\n" 

86 

87 tcl += "exit\n" 

88 return tcl 

89 

90 def _add_module_source_files(self, modules, other_arguments): 

91 tcl = "" 

92 for module in modules: 

93 vhdl_files = [] 

94 verilog_source_files = [] 

95 for hdl_file in module.get_synthesis_files(**other_arguments): 

96 if hdl_file.is_vhdl: 

97 vhdl_files.append(hdl_file.path) 

98 elif hdl_file.is_verilog_source: 

99 verilog_source_files.append(hdl_file.path) 

100 else: 

101 raise NotImplementedError(f"Can not handle file: {hdl_file}") 

102 # Verilog headers do not need to be handled at all if the 

103 # source file that uses them is in the same directory. If 

104 # it is not, the path needs to be added to include_dirs with 

105 # a tcl command like: 

106 # set_property include_dirs {/some/path /some/other/path} [current_fileset] 

107 # See https://www.xilinx.com/support/answers/54006.html 

108 

109 # Encrypted source files (verilog (.vp?), VHDL) I do not know how 

110 # to handle, since I have no use case for it at the moment. 

111 

112 if vhdl_files: 

113 files_string = self._to_file_list(vhdl_files) 

114 tcl += f"read_vhdl -library {module.library_name} -vhdl2008 {files_string}\n" 

115 if verilog_source_files: 

116 files_string = self._to_file_list(verilog_source_files) 

117 tcl += f"read_verilog {files_string}\n" 

118 

119 return tcl 

120 

121 @staticmethod 

122 def _to_file_list(file_paths): 

123 """ 

124 Return a TCL snippet for a file list, with each file enclosed in curly braces. 

125 E.g. "{file1}" or "{{file1} {file2} {file3}}" 

126 """ 

127 if len(file_paths) == 1: 

128 files_string = to_tcl_path(file_paths[0]) 

129 else: 

130 files_string = " ".join([f"{{{to_tcl_path(file_path)}}}" for file_path in file_paths]) 

131 

132 return f"{{{files_string}}}" 

133 

134 @staticmethod 

135 def _add_tcl_sources(tcl_sources): 

136 if tcl_sources is None: 

137 return "" 

138 

139 tcl = "" 

140 for tcl_source_file in tcl_sources: 

141 tcl += f"source -notrace {{{to_tcl_path(tcl_source_file)}}}\n" 

142 return tcl 

143 

144 @staticmethod 

145 def _add_ip_cores(modules, other_arguments): 

146 tcl = "" 

147 for module in modules: 

148 for ip_core_file in module.get_ip_core_files(**other_arguments): 

149 create_function_name = f"create_ip_core_{ip_core_file.name}" 

150 tcl += f"proc {create_function_name} {{}} {{\n" 

151 

152 if ip_core_file.variables: 

153 for key, value in ip_core_file.variables.items(): 

154 tcl += f' set {key} "{value}"\n' 

155 

156 tcl += f"""\ 

157 source -notrace {{{to_tcl_path(ip_core_file.path)}}} 

158}} 

159{create_function_name} 

160""" 

161 

162 return tcl 

163 

164 def _add_build_step_hooks(self, build_step_hooks, project_folder): 

165 if build_step_hooks is None: 

166 return "" 

167 

168 # There can be many hooks for the same step. Reorganize them into a dict, according 

169 # to the format step_name: [list of hooks] 

170 hook_steps = dict() 

171 for build_step_hook in build_step_hooks: 

172 if build_step_hook.hook_step in hook_steps: 

173 hook_steps[build_step_hook.hook_step].append(build_step_hook) 

174 else: 

175 hook_steps[build_step_hook.hook_step] = [build_step_hook] 

176 

177 tcl = "" 

178 for step, hooks in hook_steps.items(): 

179 # Vivado will only accept one TCL script as hook for each step. So if we want 

180 # to add more we have to create a new TCL file, that sources the other files, 

181 # and add that as the hook to Vivado. 

182 if len(hooks) == 1: 

183 tcl_file = hooks[0].tcl_file 

184 else: 

185 tcl_file = project_folder / ("hook_" + step.replace(".", "_") + ".tcl") 

186 source_hooks_tcl = "".join( 

187 [f"source {{{to_tcl_path(hook.tcl_file)}}}\n" for hook in hooks] 

188 ) 

189 create_file(tcl_file, source_hooks_tcl) 

190 

191 # Add to fileset to enable archive and other project based functionality 

192 tcl += f"add_files -fileset utils_1 -norecurse {{{to_tcl_path(tcl_file)}}}\n" 

193 

194 # Build step hook can only be applied to a run (e.g. impl_1), not on a project basis 

195 run_wildcard = "synth_*" if hooks[0].step_is_synth else "impl_*" 

196 tcl_block = f"set_property {step} {{{to_tcl_path(tcl_file)}}} ${{run}}" 

197 tcl += self._tcl_for_each_run(run_wildcard, tcl_block) 

198 

199 return tcl 

200 

201 def _add_project_settings(self): 

202 tcl = "" 

203 

204 # Default value for when opening project in GUI. 

205 # Will be overwritten if using build() function. 

206 tcl += "set_param general.maxThreads 7\n" 

207 

208 # Enable VHDL assert statements to be evaluated. A severity level of failure will 

209 # stop the synthesis and produce an error. 

210 tcl_block = "set_property STEPS.SYNTH_DESIGN.ARGS.ASSERT true ${run}" 

211 tcl += self._tcl_for_each_run("synth_*", tcl_block) 

212 

213 # Enable binary bitstream as well 

214 tcl_block = "set_property STEPS.WRITE_BITSTREAM.ARGS.BIN_FILE true ${run}" 

215 tcl += self._tcl_for_each_run("impl_*", tcl_block) 

216 

217 return tcl 

218 

219 @staticmethod 

220 def _tcl_for_each_run(run_wildcard, tcl_block): 

221 """ 

222 Apply TCL block for each defined run. Use ${run} for run variable in TCL. 

223 """ 

224 tcl = "" 

225 tcl += f"foreach run [get_runs {run_wildcard}] {{\n" 

226 tcl += tcl_block + "\n" 

227 tcl += "}\n" 

228 return tcl 

229 

230 @staticmethod 

231 def _add_generics(generics): 

232 """ 

233 Generics are set according to this weird format: 

234 https://www.xilinx.com/support/answers/52217.html 

235 """ 

236 if not generics: 

237 return "" 

238 

239 generic_list = [] 

240 for name, value in generics.items(): 

241 if isinstance(value, bool): 

242 value_tcl_formatted = "1'b1" if value else "1'b0" 

243 generic_list.append(f"{name}={value_tcl_formatted}") 

244 else: 

245 generic_list.append(f"{name}={value}") 

246 

247 generics_string = " ".join(generic_list) 

248 return f"set_property generic {{{generics_string}}} [current_fileset]\n" 

249 

250 @staticmethod 

251 def _iterate_constraints(modules, constraints, other_arguments): 

252 for module in modules: 

253 for constraint in module.get_scoped_constraints(**other_arguments): 

254 yield constraint 

255 

256 if constraints is not None: 

257 for constraint in constraints: 

258 yield constraint 

259 

260 @staticmethod 

261 def _add_constraints(constraints): 

262 tcl = "" 

263 for constraint in constraints: 

264 constraint_file = to_tcl_path(constraint.file) 

265 

266 ref_flags = "" if constraint.ref is None else (f"-ref {constraint.ref} ") 

267 managed_flags = "" if constraint_file.endswith("xdc") else "-unmanaged " 

268 tcl += f"read_xdc {ref_flags}{managed_flags}{{{constraint_file}}}\n" 

269 

270 get_file = f"[get_files {{{constraint_file}}}]" 

271 tcl += f"set_property PROCESSING_ORDER {constraint.processing_order} {get_file}\n" 

272 

273 if constraint.used_in == "impl": 

274 tcl += f"set_property used_in_synthesis false {get_file}\n" 

275 elif constraint.used_in == "synth": 

276 tcl += f"set_property used_in_implementation false {get_file}\n" 

277 

278 return tcl 

279 

280 def build( 

281 self, 

282 project_file, 

283 output_path, 

284 num_threads, 

285 run_index, 

286 generics=None, 

287 synth_only=False, 

288 analyze_synthesis_timing=True, 

289 ): 

290 # Max value in Vivado 2018.3+. set_param will give an error if higher number. 

291 num_threads_general = min(num_threads, 32) 

292 num_threads_synth = min(num_threads, 8) 

293 

294 synth_run = f"synth_{run_index}" 

295 impl_run = f"impl_{run_index}" 

296 

297 tcl = f"open_project {to_tcl_path(project_file)}\n" 

298 tcl += f"set_param general.maxThreads {num_threads_general}\n" 

299 tcl += f"set_param synth.maxThreads {num_threads_synth}\n" 

300 tcl += "\n" 

301 tcl += self._add_generics(generics) 

302 tcl += "\n" 

303 tcl += self._synthesis(synth_run, num_threads, analyze_synthesis_timing) 

304 tcl += "\n" 

305 if not synth_only: 

306 tcl += self._run(impl_run, num_threads, to_step="write_bitstream") 

307 tcl += "\n" 

308 tcl += self._write_hw_platform(output_path) 

309 tcl += "\n" 

310 tcl += "exit\n" 

311 return tcl 

312 

313 def _synthesis(self, run, num_threads, analyze_synthesis_timing): 

314 tcl = self._run(run, num_threads) 

315 if analyze_synthesis_timing: 

316 # For synthesis flow we perform the timing checks by opening the design. It would have 

317 # been more efficient to use a post-synthesis hook (since the design would already be 

318 # open), if that mechanism had worked. It seems to be very bugged. So we add the 

319 # checkers to the build script. 

320 # For implementation, we use a pre-bitstream build hook which seems to work decently. 

321 tcl += """ 

322open_run ${run} 

323set run_directory [get_property DIRECTORY [get_runs ${run}]] 

324 

325# This call is duplicated in report_utilization.tcl for implementation. 

326set output_file [file join ${run_directory} "hierarchical_utilization.rpt"] 

327report_utilization -hierarchical -hierarchical_depth 4 -file ${output_file} 

328 

329 

330# After synthesis we check for unhandled clock crossings and abort the build based on the result. 

331# Other timing checks, e.g. setup/hold/pulse width violations, are not reliable after synthesis, 

332# and should not abort the build. These need to be checked after implementation. 

333""" 

334 

335 tcl += """ 

336# This code is duplicated in check_timing.tcl for implementation. 

337if {[regexp {\\(unsafe\\)} [report_clock_interaction -delay_type min_max -return_string]]} { 

338 puts "ERROR: Unhandled clock crossing in ${run} run. See clock_interaction.rpt and \ 

339timing_summary.rpt in ${run_directory}." 

340 

341 set output_file [file join ${run_directory} "clock_interaction.rpt"] 

342 report_clock_interaction -delay_type min_max -file ${output_file} 

343 

344 set output_file [file join ${run_directory} "timing_summary.rpt"] 

345 report_timing_summary -file ${output_file} 

346 

347 exit 1 

348} 

349""" 

350 return tcl 

351 

352 @staticmethod 

353 def _run(run, num_threads, to_step=None): 

354 to_step = "" if to_step is None else " -to_step " + to_step 

355 

356 tcl = f""" 

357set run {run} 

358reset_run ${{run}} 

359launch_runs ${{run}} -jobs {num_threads}{to_step} 

360""" 

361 

362 tcl += """ 

363wait_on_run ${run} 

364 

365if {[get_property PROGRESS [get_runs ${run}]] != "100%"} { 

366 puts "ERROR: Run ${run} failed." 

367 exit 1 

368} 

369""" 

370 return tcl 

371 

372 def _write_hw_platform(self, output_path): 

373 xsa_file = to_tcl_path(output_path / (self.name + ".xsa")) 

374 tcl = f"write_hw_platform -fixed -force {{{xsa_file}}}\n" 

375 return tcl