Coverage for tsfpga/vivado/test/test_tcl.py: 100%

157 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-02-21 20:51 +0000

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

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

3# 

4# This file is part of the tsfpga project, a project platform for modern FPGA development. 

5# https://tsfpga.com 

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

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

8 

9from collections import OrderedDict 

10from pathlib import Path 

11from unittest.mock import MagicMock 

12 

13import pytest 

14 

15from tsfpga.build_step_tcl_hook import BuildStepTclHook 

16from tsfpga.ip_core_file import IpCoreFile 

17from tsfpga.module import BaseModule, get_modules 

18from tsfpga.system_utils import create_file 

19from tsfpga.test.test_utils import file_contains_string 

20from tsfpga.vivado.common import to_tcl_path 

21from tsfpga.vivado.generics import BitVectorGenericValue, StringGenericValue 

22from tsfpga.vivado.tcl import VivadoTcl 

23 

24 

25def test_set_create_run_index(): 

26 tcl = VivadoTcl(name="").create(project_folder=Path(), modules=[], part="", top="", run_index=2) 

27 assert '\ncurrent_run [get_runs "synth_2"]\n' in tcl 

28 

29 

30def test_static_generics(): 

31 # Use OrderedDict here in test so that order will be preserved and we can test for equality. 

32 # In real world case a normal dict can be used. 

33 generics = OrderedDict( 

34 enable=True, 

35 disable=False, 

36 integer=123, 

37 slv=BitVectorGenericValue("0101"), 

38 string=StringGenericValue("apa"), 

39 ) 

40 

41 tcl = VivadoTcl(name="").create( 

42 project_folder=Path(), modules=[], part="", top="", run_index=1, generics=generics 

43 ) 

44 expected = ( 

45 '\nset_property "generic" ' 

46 "{enable=1'b1 disable=1'b0 integer=123 slv=4'b0101 string=\"apa\"} " 

47 "[current_fileset]\n" 

48 ) 

49 assert expected in tcl 

50 

51 

52def test_build_step_hooks(): 

53 dummy = BuildStepTclHook(Path("dummy.tcl"), "STEPS.SYNTH_DESIGN.TCL.PRE") 

54 files = BuildStepTclHook(Path("files.tcl"), "STEPS.ROUTE_DESIGN.TCL.PRE") 

55 tcl = VivadoTcl(name="").create( 

56 project_folder=Path(), 

57 modules=[], 

58 part="part", 

59 top="", 

60 run_index=1, 

61 build_step_hooks=[dummy, files], 

62 ) 

63 

64 assert ( 

65 f"\n " 

66 f'set_property "STEPS.SYNTH_DESIGN.TCL.PRE" { {to_tcl_path(dummy.tcl_file)}} ${ run} \n' 

67 ) in tcl 

68 assert ( 

69 f"\n " 

70 f'set_property "STEPS.ROUTE_DESIGN.TCL.PRE" { {to_tcl_path(files.tcl_file)}} ${ run} \n' 

71 ) in tcl 

72 

73 

74def test_build_step_hooks_with_same_hook_step(tmp_path): 

75 dummy = BuildStepTclHook(Path("dummy.tcl"), "STEPS.SYNTH_DESIGN.TCL.PRE") 

76 files = BuildStepTclHook(Path("files.tcl"), "STEPS.SYNTH_DESIGN.TCL.PRE") 

77 tcl = VivadoTcl(name="").create( 

78 project_folder=tmp_path / "dummy_project_folder", 

79 modules=[], 

80 part="part", 

81 top="", 

82 run_index=1, 

83 build_step_hooks=[dummy, files], 

84 ) 

85 

86 hook_file = tmp_path / "dummy_project_folder" / "hook_STEPS_SYNTH_DESIGN_TCL_PRE.tcl" 

87 

88 assert file_contains_string(hook_file, f"source { {to_tcl_path(dummy.tcl_file)}} ") 

89 assert file_contains_string(hook_file, f"source { {to_tcl_path(files.tcl_file)}} ") 

90 

91 assert ( 

92 f'\n set_property "STEPS.SYNTH_DESIGN.TCL.PRE" { {to_tcl_path(hook_file)}} ${ run} \n' 

93 in tcl 

94 ) 

95 

96 

97def test_ip_cache_location(tmp_path): 

98 tcl = VivadoTcl(name="").create( 

99 project_folder=Path(), modules=[], part="part", top="", run_index=1 

100 ) 

101 assert "config_ip_cache" not in tcl 

102 

103 tcl = VivadoTcl(name="").create( 

104 project_folder=Path(), modules=[], part="part", top="", run_index=1, ip_cache_path=tmp_path 

105 ) 

106 assert f"\nconfig_ip_cache -use_cache_location { {to_tcl_path(tmp_path)}} \n" in tcl 

107 

108 

109def test_multiple_threads_is_capped_by_vivado_limits(): 

110 num_threads = 128 

111 tcl = VivadoTcl(name="").build( 

112 project_file=Path(), output_path=Path(), num_threads=num_threads, run_index=1 

113 ) 

114 assert 'set_param "general.maxThreads" 32' in tcl 

115 assert 'set_param "synth.maxThreads" 8' in tcl 

116 assert "launch_runs ${run} -jobs 128" in tcl 

117 assert "launch_runs ${run} -jobs 128" in tcl 

118 

119 

120def test_set_build_run_index(): 

121 tcl = VivadoTcl(name="").build( 

122 project_file=Path(), output_path=Path(), num_threads=0, run_index=1 

123 ) 

124 assert "impl_1" in tcl 

125 assert "synth_1" in tcl 

126 assert "impl_2" not in tcl 

127 assert "synth_2" not in tcl 

128 

129 tcl = VivadoTcl(name="").build( 

130 project_file=Path(), output_path=Path(), num_threads=0, run_index=2 

131 ) 

132 assert "impl_2" in tcl 

133 assert "synth_2" in tcl 

134 assert "impl_1" not in tcl 

135 assert "synth_1" not in tcl 

136 

137 

138def test_runtime_generics(): 

139 tcl = VivadoTcl(name="").build( 

140 project_file=Path(), 

141 output_path=Path(), 

142 num_threads=0, 

143 run_index=0, 

144 generics={"dummy": True}, 

145 ) 

146 expected = '\nset_property "generic" {dummy=1\'b1} [current_fileset]\n' 

147 assert expected in tcl 

148 

149 

150def test_build_with_synth_only(): 

151 tcl = VivadoTcl(name="").build( 

152 project_file=Path(), output_path=Path(), num_threads=0, run_index=0, synth_only=False 

153 ) 

154 assert "synth_" in tcl 

155 assert "impl_" in tcl 

156 

157 tcl = VivadoTcl(name="").build( 

158 project_file=Path(), output_path=Path(), num_threads=0, run_index=0, synth_only=True 

159 ) 

160 assert "synth_" in tcl 

161 assert "impl_" not in tcl 

162 

163 

164def test_build_with_from_impl(): 

165 tcl = VivadoTcl(name="").build( 

166 project_file=Path(), output_path=Path(), num_threads=0, run_index=0, from_impl=False 

167 ) 

168 assert "synth_" in tcl 

169 assert "impl_" in tcl 

170 

171 tcl = VivadoTcl(name="").build( 

172 project_file=Path(), output_path=Path(), num_threads=0, run_index=0, from_impl=True 

173 ) 

174 assert "synth_" not in tcl 

175 assert "impl_" in tcl 

176 

177 

178def test_module_getters_are_called_with_correct_arguments(): 

179 modules = [MagicMock(spec=BaseModule)] 

180 VivadoTcl(name="").create( 

181 project_folder=Path(), 

182 modules=modules, 

183 part="", 

184 top="", 

185 run_index=1, 

186 other_arguments={"apa": 123, "hest": 456}, 

187 ) 

188 

189 modules[0].get_synthesis_files.assert_called_once_with(apa=123, hest=456) 

190 modules[0].get_scoped_constraints.assert_called_once_with(apa=123, hest=456) 

191 modules[0].get_ip_core_files.assert_called_once_with(apa=123, hest=456) 

192 

193 

194@pytest.fixture 

195def vivado_tcl_test(tmp_path): 

196 class VivadoTclTest: 

197 def __init__(self): 

198 self.modules_folder = tmp_path / "modules" 

199 

200 # A library with some synth files and some test files 

201 self.a_vhd = to_tcl_path(create_file(self.modules_folder / "apa" / "a.vhd")) 

202 self.b_vhd = to_tcl_path(create_file(self.modules_folder / "apa" / "b.vhd")) 

203 self.tb_a_vhd = to_tcl_path( 

204 create_file(self.modules_folder / "apa" / "test" / "tb_a.vhd") 

205 ) 

206 self.a_xdc = to_tcl_path( 

207 create_file(self.modules_folder / "apa" / "scoped_constraints" / "a.xdc") 

208 ) 

209 

210 self.c_v = to_tcl_path(create_file(self.modules_folder / "apa" / "c.v")) 

211 self.b_tcl = to_tcl_path( 

212 create_file(self.modules_folder / "apa" / "scoped_constraints" / "b.tcl") 

213 ) 

214 

215 self.c_tcl = to_tcl_path( 

216 create_file(self.modules_folder / "apa" / "ip_cores" / "c.tcl") 

217 ) 

218 

219 # A library with only test files 

220 self.d_vhd = to_tcl_path(create_file(self.modules_folder / "zebra" / "test" / "d.vhd")) 

221 

222 self.modules = get_modules(self.modules_folder) 

223 

224 self.tcl = VivadoTcl(name="name") 

225 

226 return VivadoTclTest() 

227 

228 

229def test_source_file_list_is_correctly_formatted(vivado_tcl_test): 

230 tcl = vivado_tcl_test.tcl.create( 

231 project_folder=Path(), modules=vivado_tcl_test.modules, part="", top="", run_index=1 

232 ) 

233 

234 # Order of files is not really deterministic 

235 expected_1 = ( 

236 '\nread_vhdl -library "apa" -vhdl2008 ' 

237 f"{ { {vivado_tcl_test.b_vhd}} { {vivado_tcl_test.a_vhd}} } \n" 

238 ) 

239 expected_2 = ( 

240 '\nread_vhdl -library "apa" -vhdl2008 ' 

241 f"{ { {vivado_tcl_test.a_vhd}} { {vivado_tcl_test.b_vhd}} } \n" 

242 ) 

243 assert expected_1 in tcl or expected_2 in tcl 

244 

245 expected = f"\nread_verilog { {vivado_tcl_test.c_v}} \n" 

246 assert expected in tcl 

247 

248 

249def test_only_synthesis_files_added_to_create_project_tcl(vivado_tcl_test): 

250 tcl = vivado_tcl_test.tcl.create( 

251 project_folder=Path(), modules=vivado_tcl_test.modules, part="", top="", run_index=1 

252 ) 

253 assert vivado_tcl_test.a_vhd in tcl 

254 assert vivado_tcl_test.c_v in tcl 

255 assert vivado_tcl_test.tb_a_vhd not in tcl 

256 assert "tb_a.vhd" not in tcl 

257 

258 

259def test_constraints(vivado_tcl_test): 

260 tcl = vivado_tcl_test.tcl.create( 

261 project_folder=Path(), modules=vivado_tcl_test.modules, part="part", top="", run_index=1 

262 ) 

263 

264 expected = f'\nread_xdc -ref "a" { {vivado_tcl_test.a_xdc}} \n' 

265 assert expected in tcl 

266 expected = f'\nread_xdc -ref "b" -unmanaged { {vivado_tcl_test.b_tcl}} \n' 

267 assert expected in tcl 

268 

269 

270def test_ip_core_files(vivado_tcl_test): 

271 ip_core_file_path = vivado_tcl_test.modules_folder.parent / "my_name.tcl" 

272 module = MagicMock(spec=BaseModule) 

273 module.get_ip_core_files.return_value = [ 

274 IpCoreFile(path=ip_core_file_path, apa="hest", zebra=123) 

275 ] 

276 

277 vivado_tcl_test.modules.append(module) 

278 

279 tcl = vivado_tcl_test.tcl.create( 

280 project_folder=Path(), modules=vivado_tcl_test.modules, part="part", top="", run_index=1 

281 ) 

282 

283 assert ( 

284 f""" 

285proc create_ip_core_c { } { 

286 source -notrace { {vivado_tcl_test.c_tcl}} 

287} 

288create_ip_core_c 

289""" 

290 in tcl 

291 ) 

292 

293 assert ( 

294 f""" 

295proc create_ip_core_my_name { } { 

296 set apa "hest" 

297 set zebra "123" 

298 source -notrace { {to_tcl_path(ip_core_file_path)}} 

299} 

300create_ip_core_my_name 

301""" 

302 in tcl 

303 ) 

304 

305 

306def test_create_with_ip_cores_only(vivado_tcl_test): 

307 tcl = vivado_tcl_test.tcl.create( 

308 project_folder=Path(), 

309 modules=vivado_tcl_test.modules, 

310 part="part", 

311 top="", 

312 run_index=1, 

313 ip_cores_only=True, 

314 ) 

315 assert vivado_tcl_test.c_tcl in tcl 

316 assert vivado_tcl_test.a_vhd not in tcl 

317 

318 

319def test_empty_library_not_in_create_project_tcl(vivado_tcl_test): 

320 tcl = vivado_tcl_test.tcl.create( 

321 project_folder=Path(), modules=vivado_tcl_test.modules, part="part", top="", run_index=1 

322 ) 

323 assert "zebra" not in tcl 

324 

325 

326def test_multiple_tcl_sources(vivado_tcl_test): 

327 extra_tcl_sources = [Path("dummy.tcl"), Path("files.tcl")] 

328 tcl = vivado_tcl_test.tcl.create( 

329 project_folder=Path(), 

330 modules=vivado_tcl_test.modules, 

331 part="part", 

332 top="", 

333 run_index=1, 

334 tcl_sources=extra_tcl_sources, 

335 ) 

336 

337 for filename in extra_tcl_sources: 

338 assert f"\nsource -notrace { {to_tcl_path(filename)}} \n" in tcl 

339 

340 

341def test_io_buffer_setting(vivado_tcl_test): 

342 tcl = vivado_tcl_test.tcl.create( 

343 project_folder=Path(), 

344 modules=vivado_tcl_test.modules, 

345 part="part", 

346 top="", 

347 run_index=1, 

348 disable_io_buffers=True, 

349 ) 

350 

351 no_io_buffers_tcl = ( 

352 '\nset_property -name "STEPS.SYNTH_DESIGN.ARGS.MORE OPTIONS" ' 

353 '-value "-no_iobuf" -objects [get_runs "synth_1"]\n' 

354 ) 

355 assert no_io_buffers_tcl in tcl 

356 

357 tcl = vivado_tcl_test.tcl.create( 

358 project_folder=Path(), 

359 modules=vivado_tcl_test.modules, 

360 part="part", 

361 top="", 

362 run_index=1, 

363 disable_io_buffers=False, 

364 ) 

365 

366 assert no_io_buffers_tcl not in tcl 

367 

368 

369def test_analyze_synthesis_settings_on_and_off(vivado_tcl_test): 

370 tcl = vivado_tcl_test.tcl.build( 

371 project_file=Path(), 

372 output_path=Path(), 

373 num_threads=1, 

374 run_index=1, 

375 analyze_synthesis_timing=True, 

376 ) 

377 assert "open_run" in tcl 

378 assert "report_clock_interaction" in tcl 

379 

380 tcl = vivado_tcl_test.tcl.build( 

381 project_file=Path(), 

382 output_path=Path(), 

383 num_threads=1, 

384 run_index=1, 

385 analyze_synthesis_timing=False, 

386 ) 

387 # When disabled, the run should not even be opened, which saves time 

388 assert "open_run" not in tcl 

389 assert "report_clock_interaction" not in tcl 

390 

391 

392def test_impl_explore(vivado_tcl_test): 

393 num_runs = 4 

394 

395 tcl = vivado_tcl_test.tcl.build( 

396 project_file=Path(), 

397 output_path=Path(), 

398 num_threads=num_runs, 

399 run_index=1, 

400 impl_explore=True, 

401 ) 

402 

403 assert ( 

404 f'launch_runs -jobs {num_runs} [get_runs "impl_explore_*"] -to_step "write_bitstream"' 

405 in tcl 

406 ) 

407 assert ( 

408 'wait_on_runs -quiet -exit_condition ANY_ONE_MET_TIMING [get_runs "impl_explore_*"]' in tcl 

409 ) 

410 assert 'reset_runs [get_runs -filter {STATUS == "Queued..."}]' in tcl 

411 assert ( 

412 'wait_on_runs -quiet [get_runs -filter {STATUS != "Not started"} "impl_explore_*"]' in tcl 

413 ) 

414 assert 'foreach run [get_runs -filter {PROGRESS == "100%"} "impl_explore_*"]' in tcl