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

149 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-11-29 20:01 +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://gitlab.com/tsfpga/tsfpga 

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

8 

9# Standard libraries 

10import unittest 

11from collections import OrderedDict 

12from pathlib import Path 

13from unittest.mock import MagicMock 

14 

15# Third party libraries 

16import pytest 

17 

18# First party libraries 

19from tsfpga.build_step_tcl_hook import BuildStepTclHook 

20from tsfpga.ip_core_file import IpCoreFile 

21from tsfpga.module import BaseModule, get_modules 

22from tsfpga.system_utils import create_file 

23from tsfpga.test import file_contains_string 

24 

25# pylint: disable=unused-import 

26from tsfpga.test.conftest import fixture_tmp_path # noqa: F401 

27from tsfpga.vivado.common import to_tcl_path 

28from tsfpga.vivado.generics import BitVectorGenericValue, StringGenericValue 

29from tsfpga.vivado.tcl import VivadoTcl 

30 

31 

32def test_set_create_run_index(): 

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

34 assert "\ncurrent_run [get_runs synth_2]\n" in tcl 

35 

36 

37def test_static_generics(): 

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

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

40 generics = OrderedDict( 

41 enable=True, 

42 disable=False, 

43 integer=123, 

44 slv=BitVectorGenericValue("0101"), 

45 string=StringGenericValue("apa"), 

46 ) 

47 

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

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

50 ) 

51 expected = ( 

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

53 "[current_fileset]\n" 

54 ) 

55 assert expected in tcl 

56 

57 

58def test_build_step_hooks(): 

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

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

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

62 project_folder=Path(), 

63 modules=[], 

64 part="part", 

65 top="", 

66 run_index=1, 

67 build_step_hooks=[dummy, files], 

68 ) 

69 

70 assert ( 

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

72 in tcl 

73 ) 

74 assert ( 

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

76 in tcl 

77 ) 

78 

79 

80def test_build_step_hooks_with_same_hook_step(tmp_path): 

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

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

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

84 project_folder=tmp_path / "dummy_project_folder", 

85 modules=[], 

86 part="part", 

87 top="", 

88 run_index=1, 

89 build_step_hooks=[dummy, files], 

90 ) 

91 

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

93 

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

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

96 

97 assert ( 

98 f"\nset_property STEPS.SYNTH_DESIGN.TCL.PRE {{{to_tcl_path(hook_file)}}} ${{run}}\n" 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 print(tcl) 

120 assert "set_param general.maxThreads 32" in tcl 

121 assert "set_param synth.maxThreads 8" in tcl 

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

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

124 

125 

126def test_set_build_run_index(): 

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

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

129 ) 

130 assert "impl_1" in tcl 

131 assert "synth_1" in tcl 

132 assert "impl_2" not in tcl 

133 assert "synth_2" not in tcl 

134 

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

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

137 ) 

138 assert "impl_2" in tcl 

139 assert "synth_2" in tcl 

140 assert "impl_1" not in tcl 

141 assert "synth_1" not in tcl 

142 

143 

144def test_runtime_generics(): 

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

146 project_file=Path(), 

147 output_path=Path(), 

148 num_threads=0, 

149 run_index=0, 

150 generics=dict(dummy=True), 

151 ) 

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

153 assert expected in tcl 

154 

155 

156def test_build_with_synth_only(): 

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

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

159 ) 

160 assert "synth_" in tcl 

161 assert "impl_" in tcl 

162 

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

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

165 ) 

166 assert "synth_" in tcl 

167 assert "impl_" not in tcl 

168 

169 

170def test_build_with_from_impl(): 

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

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

173 ) 

174 assert "synth_" in tcl 

175 assert "impl_" in tcl 

176 

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

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

179 ) 

180 assert "synth_" not in tcl 

181 assert "impl_" in tcl 

182 

183 

184def test_module_getters_are_called_with_correct_arguments(): 

185 modules = [MagicMock(spec=BaseModule)] 

186 VivadoTcl(name="").create( 

187 project_folder=Path(), 

188 modules=modules, 

189 part="", 

190 top="", 

191 run_index=1, 

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

193 ) 

194 

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

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

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

198 

199 

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

201@pytest.mark.usefixtures("fixture_tmp_path") 

202class TestVivadoTcl(unittest.TestCase): 

203 

204 tmp_path = None 

205 

206 def setUp(self): 

207 self.modules_folder = self.tmp_path / "modules" 

208 

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

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

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

212 self.tb_a_vhd = to_tcl_path(create_file(self.modules_folder / "apa" / "test" / "tb_a.vhd")) 

213 self.a_xdc = to_tcl_path( 

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

215 ) 

216 

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

218 self.b_tcl = to_tcl_path( 

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

220 ) 

221 

222 self.c_tcl = to_tcl_path(create_file(self.modules_folder / "apa" / "ip_cores" / "c.tcl")) 

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 def test_source_file_list_is_correctly_formatted(self): 

232 tcl = self.tcl.create( 

233 project_folder=Path(), modules=self.modules, part="", top="", run_index=1 

234 ) 

235 

236 # Order of files is not really deterministic 

237 expected_1 = f"\nread_vhdl -library apa -vhdl2008 {{{{{self.b_vhd}}} {{{self.a_vhd}}}}}\n" 

238 expected_2 = f"\nread_vhdl -library apa -vhdl2008 {{{{{self.a_vhd}}} {{{self.b_vhd}}}}}\n" 

239 assert expected_1 in tcl or expected_2 in tcl 

240 

241 expected = f"\nread_verilog {{{self.c_v}}}\n" 

242 assert expected in tcl 

243 

244 def test_only_synthesis_files_added_to_create_project_tcl(self): 

245 tcl = self.tcl.create( 

246 project_folder=Path(), modules=self.modules, part="", top="", run_index=1 

247 ) 

248 assert self.a_vhd in tcl and self.c_v in tcl 

249 assert self.tb_a_vhd not in tcl and "tb_a.vhd" not in tcl 

250 

251 def test_constraints(self): 

252 tcl = self.tcl.create( 

253 project_folder=Path(), modules=self.modules, part="part", top="", run_index=1 

254 ) 

255 

256 expected = f"\nread_xdc -ref a {{{self.a_xdc}}}\n" 

257 assert expected in tcl 

258 expected = f"\nread_xdc -ref b -unmanaged {{{self.b_tcl}}}\n" 

259 assert expected in tcl 

260 

261 def test_ip_core_files(self): 

262 ip_core_file_path = self.tmp_path / "my_name.tcl" 

263 module = MagicMock(spec=BaseModule) 

264 module.get_ip_core_files.return_value = [ 

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

266 ] 

267 

268 self.modules.append(module) 

269 

270 tcl = self.tcl.create( 

271 project_folder=Path(), modules=self.modules, part="part", top="", run_index=1 

272 ) 

273 

274 assert ( 

275 f""" 

276proc create_ip_core_c {{}} {{ 

277 source -notrace {{{self.c_tcl}}} 

278}} 

279create_ip_core_c 

280""" 

281 in tcl 

282 ) 

283 

284 assert ( 

285 f""" 

286proc create_ip_core_my_name {{}} {{ 

287 set apa "hest" 

288 set zebra "123" 

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

290}} 

291create_ip_core_my_name 

292""" 

293 in tcl 

294 ) 

295 

296 def test_create_with_ip_cores_only(self): 

297 tcl = self.tcl.create( 

298 project_folder=Path(), 

299 modules=self.modules, 

300 part="part", 

301 top="", 

302 run_index=1, 

303 ip_cores_only=True, 

304 ) 

305 assert self.c_tcl in tcl 

306 assert self.a_vhd not in tcl 

307 

308 def test_empty_library_not_in_create_project_tcl(self): 

309 tcl = self.tcl.create( 

310 project_folder=Path(), modules=self.modules, part="part", top="", run_index=1 

311 ) 

312 assert "zebra" not in tcl 

313 

314 def test_multiple_tcl_sources(self): 

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

316 tcl = self.tcl.create( 

317 project_folder=Path(), 

318 modules=self.modules, 

319 part="part", 

320 top="", 

321 run_index=1, 

322 tcl_sources=extra_tcl_sources, 

323 ) 

324 

325 for filename in extra_tcl_sources: 

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

327 

328 def test_io_buffer_setting(self): 

329 tcl = self.tcl.create( 

330 project_folder=Path(), 

331 modules=self.modules, 

332 part="part", 

333 top="", 

334 run_index=1, 

335 disable_io_buffers=True, 

336 ) 

337 

338 no_io_buffers_tcl = ( 

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

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

341 ) 

342 assert no_io_buffers_tcl in tcl 

343 

344 tcl = self.tcl.create( 

345 project_folder=Path(), 

346 modules=self.modules, 

347 part="part", 

348 top="", 

349 run_index=1, 

350 disable_io_buffers=False, 

351 ) 

352 

353 assert no_io_buffers_tcl not in tcl 

354 

355 def test_analyze_synthesis_settings_on_and_off(self): 

356 tcl = self.tcl.build( 

357 project_file=Path(), 

358 output_path=Path(), 

359 num_threads=1, 

360 run_index=1, 

361 analyze_synthesis_timing=True, 

362 ) 

363 assert "open_run" in tcl 

364 assert "report_clock_interaction" in tcl 

365 

366 tcl = self.tcl.build( 

367 project_file=Path(), 

368 output_path=Path(), 

369 num_threads=1, 

370 run_index=1, 

371 analyze_synthesis_timing=False, 

372 ) 

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

374 assert "open_run" not in tcl 

375 assert "report_clock_interaction" not in tcl