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

156 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-20 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 

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 {enable=1'b1 disable=1'b0 integer=123 slv=4'b0101 string=\"apa\"} " 

51 "[current_fileset]\n" 

52 ) 

53 assert expected in tcl 

54 

55 

56def test_build_step_hooks(): 

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

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

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

60 project_folder=Path(), 

61 modules=[], 

62 part="part", 

63 top="", 

64 run_index=1, 

65 build_step_hooks=[dummy, files], 

66 ) 

67 

68 assert ( 

69 f"\nset_property STEPS.SYNTH_DESIGN.TCL.PRE {{{to_tcl_path(dummy.tcl_file)}}} ${{run}}\n" 

70 in tcl 

71 ) 

72 assert ( 

73 f"\nset_property STEPS.ROUTE_DESIGN.TCL.PRE {{{to_tcl_path(files.tcl_file)}}} ${{run}}\n" 

74 in tcl 

75 ) 

76 

77 

78def test_build_step_hooks_with_same_hook_step(tmp_path): 

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

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

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

82 project_folder=tmp_path / "dummy_project_folder", 

83 modules=[], 

84 part="part", 

85 top="", 

86 run_index=1, 

87 build_step_hooks=[dummy, files], 

88 ) 

89 

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

91 

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

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

94 

95 assert ( 

96 f"\nset_property STEPS.SYNTH_DESIGN.TCL.PRE {{{to_tcl_path(hook_file)}}} ${{run}}\n" in tcl 

97 ) 

98 

99 

100def test_ip_cache_location(tmp_path): 

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

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

103 ) 

104 assert "config_ip_cache" not in tcl 

105 

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

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

108 ) 

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

110 

111 

112def test_multiple_threads_is_capped_by_vivado_limits(): 

113 num_threads = 128 

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

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

116 ) 

117 print(tcl) 

118 assert "set_param general.maxThreads 32" in tcl 

119 assert "set_param synth.maxThreads 8" in tcl 

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

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

122 

123 

124def test_set_build_run_index(): 

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

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

127 ) 

128 assert "impl_1" in tcl 

129 assert "synth_1" in tcl 

130 assert "impl_2" not in tcl 

131 assert "synth_2" not in tcl 

132 

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

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

135 ) 

136 assert "impl_2" in tcl 

137 assert "synth_2" in tcl 

138 assert "impl_1" not in tcl 

139 assert "synth_1" not in tcl 

140 

141 

142def test_runtime_generics(): 

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

144 project_file=Path(), 

145 output_path=Path(), 

146 num_threads=0, 

147 run_index=0, 

148 generics=dict(dummy=True), 

149 ) 

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

151 assert expected in tcl 

152 

153 

154def test_build_with_synth_only(): 

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

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

157 ) 

158 assert "synth_" in tcl 

159 assert "impl_" in tcl 

160 

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

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

163 ) 

164 assert "synth_" in tcl 

165 assert "impl_" not in tcl 

166 

167 

168def test_build_with_from_impl(): 

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

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

171 ) 

172 assert "synth_" in tcl 

173 assert "impl_" in tcl 

174 

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

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

177 ) 

178 assert "synth_" not in tcl 

179 assert "impl_" in tcl 

180 

181 

182def test_module_getters_are_called_with_correct_arguments(): 

183 modules = [MagicMock(spec=BaseModule)] 

184 VivadoTcl(name="").create( 

185 project_folder=Path(), 

186 modules=modules, 

187 part="", 

188 top="", 

189 run_index=1, 

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

191 ) 

192 

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

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

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

196 

197 

198@pytest.fixture 

199def vivado_tcl_test(tmp_path): 

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

201 def __init__(self): 

202 self.modules_folder = tmp_path / "modules" 

203 

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

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

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

207 self.tb_a_vhd = to_tcl_path( 

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

209 ) 

210 self.a_xdc = to_tcl_path( 

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

212 ) 

213 

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

215 self.b_tcl = to_tcl_path( 

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

217 ) 

218 

219 self.c_tcl = to_tcl_path( 

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

221 ) 

222 

223 # A library with only test files 

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

225 

226 self.modules = get_modules([self.modules_folder]) 

227 

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

229 

230 return VivadoTclTest() 

231 

232 

233# False positive for pytest fixtures 

234# pylint: disable=redefined-outer-name 

235 

236 

237def test_source_file_list_is_correctly_formatted(vivado_tcl_test): 

238 tcl = vivado_tcl_test.tcl.create( 

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

240 ) 

241 

242 # Order of files is not really deterministic 

243 expected_1 = ( 

244 "\nread_vhdl -library apa -vhdl2008 " 

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

246 ) 

247 expected_2 = ( 

248 "\nread_vhdl -library apa -vhdl2008 " 

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

250 ) 

251 assert expected_1 in tcl or expected_2 in tcl 

252 

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

254 assert expected in tcl 

255 

256 

257def test_only_synthesis_files_added_to_create_project_tcl(vivado_tcl_test): 

258 tcl = vivado_tcl_test.tcl.create( 

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

260 ) 

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

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

263 

264 

265def test_constraints(vivado_tcl_test): 

266 tcl = vivado_tcl_test.tcl.create( 

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

268 ) 

269 

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

271 assert expected in tcl 

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

273 assert expected in tcl 

274 

275 

276def test_ip_core_files(vivado_tcl_test): 

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

278 module = MagicMock(spec=BaseModule) 

279 module.get_ip_core_files.return_value = [ 

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

281 ] 

282 

283 vivado_tcl_test.modules.append(module) 

284 

285 tcl = vivado_tcl_test.tcl.create( 

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

287 ) 

288 

289 assert ( 

290 f""" 

291proc create_ip_core_c {{}} {{ 

292 source -notrace {{{vivado_tcl_test.c_tcl}}} 

293}} 

294create_ip_core_c 

295""" 

296 in tcl 

297 ) 

298 

299 assert ( 

300 f""" 

301proc create_ip_core_my_name {{}} {{ 

302 set apa "hest" 

303 set zebra "123" 

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

305}} 

306create_ip_core_my_name 

307""" 

308 in tcl 

309 ) 

310 

311 

312def test_create_with_ip_cores_only(vivado_tcl_test): 

313 tcl = vivado_tcl_test.tcl.create( 

314 project_folder=Path(), 

315 modules=vivado_tcl_test.modules, 

316 part="part", 

317 top="", 

318 run_index=1, 

319 ip_cores_only=True, 

320 ) 

321 assert vivado_tcl_test.c_tcl in tcl 

322 assert vivado_tcl_test.a_vhd not in tcl 

323 

324 

325def test_empty_library_not_in_create_project_tcl(vivado_tcl_test): 

326 tcl = vivado_tcl_test.tcl.create( 

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

328 ) 

329 assert "zebra" not in tcl 

330 

331 

332def test_multiple_tcl_sources(vivado_tcl_test): 

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

334 tcl = vivado_tcl_test.tcl.create( 

335 project_folder=Path(), 

336 modules=vivado_tcl_test.modules, 

337 part="part", 

338 top="", 

339 run_index=1, 

340 tcl_sources=extra_tcl_sources, 

341 ) 

342 

343 for filename in extra_tcl_sources: 

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

345 

346 

347def test_io_buffer_setting(vivado_tcl_test): 

348 tcl = vivado_tcl_test.tcl.create( 

349 project_folder=Path(), 

350 modules=vivado_tcl_test.modules, 

351 part="part", 

352 top="", 

353 run_index=1, 

354 disable_io_buffers=True, 

355 ) 

356 

357 no_io_buffers_tcl = ( 

358 "\nset_property -name {STEPS.SYNTH_DESIGN.ARGS.MORE OPTIONS} " 

359 "-value -no_iobuf -objects [get_runs synth_1]\n" 

360 ) 

361 assert no_io_buffers_tcl in tcl 

362 

363 tcl = vivado_tcl_test.tcl.create( 

364 project_folder=Path(), 

365 modules=vivado_tcl_test.modules, 

366 part="part", 

367 top="", 

368 run_index=1, 

369 disable_io_buffers=False, 

370 ) 

371 

372 assert no_io_buffers_tcl not in tcl 

373 

374 

375def test_analyze_synthesis_settings_on_and_off(vivado_tcl_test): 

376 tcl = vivado_tcl_test.tcl.build( 

377 project_file=Path(), 

378 output_path=Path(), 

379 num_threads=1, 

380 run_index=1, 

381 analyze_synthesis_timing=True, 

382 ) 

383 assert "open_run" in tcl 

384 assert "report_clock_interaction" in tcl 

385 

386 tcl = vivado_tcl_test.tcl.build( 

387 project_file=Path(), 

388 output_path=Path(), 

389 num_threads=1, 

390 run_index=1, 

391 analyze_synthesis_timing=False, 

392 ) 

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

394 assert "open_run" not in tcl 

395 assert "report_clock_interaction" not in tcl 

396 

397 

398def test_impl_explore(vivado_tcl_test): 

399 num_runs = 4 

400 

401 tcl = vivado_tcl_test.tcl.build( 

402 project_file=Path(), 

403 output_path=Path(), 

404 num_threads=num_runs, 

405 run_index=1, 

406 impl_explore=True, 

407 ) 

408 

409 assert f"launch_runs -jobs {num_runs} [get_runs impl_explore_*] -to_step write_bitstream" in tcl 

410 assert "wait_on_runs -quiet -exit_condition ANY_ONE_MET_TIMING [get_runs impl_explore_*]" in tcl 

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

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

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