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

155 statements  

« prev     ^ index     » next       coverage.py v7.6.9, created at 2024-12-20 20:52 +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 

9# Standard libraries 

10from collections import OrderedDict 

11from pathlib import Path 

12from unittest.mock import MagicMock 

13 

14# Third party libraries 

15import pytest 

16 

17# First party libraries 

18from tsfpga.build_step_tcl_hook import BuildStepTclHook 

19from tsfpga.ip_core_file import IpCoreFile 

20from tsfpga.module import BaseModule, get_modules 

21from tsfpga.system_utils import create_file 

22 

23# pylint: disable=unused-import 

24from tsfpga.test.test_utils import file_contains_string 

25from tsfpga.vivado.common import to_tcl_path 

26from tsfpga.vivado.generics import BitVectorGenericValue, StringGenericValue 

27from tsfpga.vivado.tcl import VivadoTcl 

28 

29 

30def test_set_create_run_index(): 

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

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

33 

34 

35def test_static_generics(): 

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

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

38 generics = OrderedDict( 

39 enable=True, 

40 disable=False, 

41 integer=123, 

42 slv=BitVectorGenericValue("0101"), 

43 string=StringGenericValue("apa"), 

44 ) 

45 

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

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

48 ) 

49 expected = ( 

50 '\nset_property "generic" ' 

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

52 "[current_fileset]\n" 

53 ) 

54 assert expected in tcl 

55 

56 

57def test_build_step_hooks(): 

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

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

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

61 project_folder=Path(), 

62 modules=[], 

63 part="part", 

64 top="", 

65 run_index=1, 

66 build_step_hooks=[dummy, files], 

67 ) 

68 

69 assert ( 

70 f"\n " 

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

72 ) in tcl 

73 assert ( 

74 f"\n " 

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

76 ) in tcl 

77 

78 

79def test_build_step_hooks_with_same_hook_step(tmp_path): 

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

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

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

83 project_folder=tmp_path / "dummy_project_folder", 

84 modules=[], 

85 part="part", 

86 top="", 

87 run_index=1, 

88 build_step_hooks=[dummy, files], 

89 ) 

90 

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

92 

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

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

95 

96 assert ( 

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

98 in tcl 

99 ) 

100 

101 

102def test_ip_cache_location(tmp_path): 

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

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

105 ) 

106 assert "config_ip_cache" not in tcl 

107 

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

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

110 ) 

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

112 

113 

114def test_multiple_threads_is_capped_by_vivado_limits(): 

115 num_threads = 128 

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

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

118 ) 

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

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

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

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

123 

124 

125def test_set_build_run_index(): 

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

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

128 ) 

129 assert "impl_1" in tcl 

130 assert "synth_1" in tcl 

131 assert "impl_2" not in tcl 

132 assert "synth_2" not in tcl 

133 

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

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

136 ) 

137 assert "impl_2" in tcl 

138 assert "synth_2" in tcl 

139 assert "impl_1" not in tcl 

140 assert "synth_1" not in tcl 

141 

142 

143def test_runtime_generics(): 

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

145 project_file=Path(), 

146 output_path=Path(), 

147 num_threads=0, 

148 run_index=0, 

149 generics=dict(dummy=True), 

150 ) 

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

152 assert expected in tcl 

153 

154 

155def test_build_with_synth_only(): 

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

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

158 ) 

159 assert "synth_" in tcl 

160 assert "impl_" in tcl 

161 

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

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

164 ) 

165 assert "synth_" in tcl 

166 assert "impl_" not in tcl 

167 

168 

169def test_build_with_from_impl(): 

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

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

172 ) 

173 assert "synth_" in tcl 

174 assert "impl_" in tcl 

175 

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

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

178 ) 

179 assert "synth_" not in tcl 

180 assert "impl_" in tcl 

181 

182 

183def test_module_getters_are_called_with_correct_arguments(): 

184 modules = [MagicMock(spec=BaseModule)] 

185 VivadoTcl(name="").create( 

186 project_folder=Path(), 

187 modules=modules, 

188 part="", 

189 top="", 

190 run_index=1, 

191 other_arguments=dict(apa=123, hest=456), 

192 ) 

193 

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

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

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

197 

198 

199@pytest.fixture 

200def vivado_tcl_test(tmp_path): 

201 class VivadoTclTest: # pylint: disable=too-many-instance-attributes 

202 def __init__(self): 

203 self.modules_folder = tmp_path / "modules" 

204 

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

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

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

208 self.tb_a_vhd = to_tcl_path( 

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

210 ) 

211 self.a_xdc = to_tcl_path( 

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

213 ) 

214 

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

216 self.b_tcl = to_tcl_path( 

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

218 ) 

219 

220 self.c_tcl = to_tcl_path( 

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

222 ) 

223 

224 # A library with only test files 

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

226 

227 self.modules = get_modules(self.modules_folder) 

228 

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

230 

231 return VivadoTclTest() 

232 

233 

234# False positive for pytest fixtures 

235# pylint: disable=redefined-outer-name 

236 

237 

238def test_source_file_list_is_correctly_formatted(vivado_tcl_test): 

239 tcl = vivado_tcl_test.tcl.create( 

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

241 ) 

242 

243 # Order of files is not really deterministic 

244 expected_1 = ( 

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

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

247 ) 

248 expected_2 = ( 

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

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

251 ) 

252 assert expected_1 in tcl or expected_2 in tcl 

253 

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

255 assert expected in tcl 

256 

257 

258def test_only_synthesis_files_added_to_create_project_tcl(vivado_tcl_test): 

259 tcl = vivado_tcl_test.tcl.create( 

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

261 ) 

262 assert vivado_tcl_test.a_vhd in tcl and vivado_tcl_test.c_v in tcl 

263 assert vivado_tcl_test.tb_a_vhd not in tcl and "tb_a.vhd" not in tcl 

264 

265 

266def test_constraints(vivado_tcl_test): 

267 tcl = vivado_tcl_test.tcl.create( 

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

269 ) 

270 

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

272 assert expected in tcl 

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

274 assert expected in tcl 

275 

276 

277def test_ip_core_files(vivado_tcl_test): 

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

279 module = MagicMock(spec=BaseModule) 

280 module.get_ip_core_files.return_value = [ 

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

282 ] 

283 

284 vivado_tcl_test.modules.append(module) 

285 

286 tcl = vivado_tcl_test.tcl.create( 

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

288 ) 

289 

290 assert ( 

291 f""" 

292proc create_ip_core_c { } { 

293 source -notrace { {vivado_tcl_test.c_tcl}} 

294} 

295create_ip_core_c 

296""" 

297 in tcl 

298 ) 

299 

300 assert ( 

301 f""" 

302proc create_ip_core_my_name { } { 

303 set apa "hest" 

304 set zebra "123" 

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

306} 

307create_ip_core_my_name 

308""" 

309 in tcl 

310 ) 

311 

312 

313def test_create_with_ip_cores_only(vivado_tcl_test): 

314 tcl = vivado_tcl_test.tcl.create( 

315 project_folder=Path(), 

316 modules=vivado_tcl_test.modules, 

317 part="part", 

318 top="", 

319 run_index=1, 

320 ip_cores_only=True, 

321 ) 

322 assert vivado_tcl_test.c_tcl in tcl 

323 assert vivado_tcl_test.a_vhd not in tcl 

324 

325 

326def test_empty_library_not_in_create_project_tcl(vivado_tcl_test): 

327 tcl = vivado_tcl_test.tcl.create( 

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

329 ) 

330 assert "zebra" not in tcl 

331 

332 

333def test_multiple_tcl_sources(vivado_tcl_test): 

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

335 tcl = vivado_tcl_test.tcl.create( 

336 project_folder=Path(), 

337 modules=vivado_tcl_test.modules, 

338 part="part", 

339 top="", 

340 run_index=1, 

341 tcl_sources=extra_tcl_sources, 

342 ) 

343 

344 for filename in extra_tcl_sources: 

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

346 

347 

348def test_io_buffer_setting(vivado_tcl_test): 

349 tcl = vivado_tcl_test.tcl.create( 

350 project_folder=Path(), 

351 modules=vivado_tcl_test.modules, 

352 part="part", 

353 top="", 

354 run_index=1, 

355 disable_io_buffers=True, 

356 ) 

357 

358 no_io_buffers_tcl = ( 

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

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

361 ) 

362 assert no_io_buffers_tcl in tcl 

363 

364 tcl = vivado_tcl_test.tcl.create( 

365 project_folder=Path(), 

366 modules=vivado_tcl_test.modules, 

367 part="part", 

368 top="", 

369 run_index=1, 

370 disable_io_buffers=False, 

371 ) 

372 

373 assert no_io_buffers_tcl not in tcl 

374 

375 

376def test_analyze_synthesis_settings_on_and_off(vivado_tcl_test): 

377 tcl = vivado_tcl_test.tcl.build( 

378 project_file=Path(), 

379 output_path=Path(), 

380 num_threads=1, 

381 run_index=1, 

382 analyze_synthesis_timing=True, 

383 ) 

384 assert "open_run" in tcl 

385 assert "report_clock_interaction" in tcl 

386 

387 tcl = vivado_tcl_test.tcl.build( 

388 project_file=Path(), 

389 output_path=Path(), 

390 num_threads=1, 

391 run_index=1, 

392 analyze_synthesis_timing=False, 

393 ) 

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

395 assert "open_run" not in tcl 

396 assert "report_clock_interaction" not in tcl 

397 

398 

399def test_impl_explore(vivado_tcl_test): 

400 num_runs = 4 

401 

402 tcl = vivado_tcl_test.tcl.build( 

403 project_file=Path(), 

404 output_path=Path(), 

405 num_threads=num_runs, 

406 run_index=1, 

407 impl_explore=True, 

408 ) 

409 

410 assert ( 

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

412 in tcl 

413 ) 

414 assert ( 

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

416 ) 

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

418 assert ( 

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

420 ) 

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