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

149 statements  

« prev     ^ index     » next       coverage.py v6.4, created at 2022-05-28 04:01 +0000

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 collections import OrderedDict 

10from pathlib import Path 

11import unittest 

12from unittest.mock import MagicMock 

13 

14import pytest 

15 

16from tsfpga.build_step_tcl_hook import BuildStepTclHook 

17from tsfpga.ip_core_file import IpCoreFile 

18from tsfpga.module import BaseModule, get_modules 

19from tsfpga.system_utils import create_file 

20from tsfpga.vivado.common import to_tcl_path 

21from tsfpga.vivado.generics import BitVectorGenericValue, StringGenericValue 

22from tsfpga.vivado.tcl import VivadoTcl 

23from tsfpga.test import file_contains_string 

24 

25# pylint: disable=unused-import 

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

27 

28 

29def test_set_create_run_index(): 

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

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

32 

33 

34def test_static_generics(): 

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

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

37 generics = OrderedDict( 

38 enable=True, 

39 disable=False, 

40 integer=123, 

41 slv=BitVectorGenericValue("0101"), 

42 string=StringGenericValue("apa"), 

43 ) 

44 

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

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

47 ) 

48 expected = ( 

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

50 "[current_fileset]\n" 

51 ) 

52 assert expected in tcl 

53 

54 

55def test_build_step_hooks(): 

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

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

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

59 project_folder=Path(), 

60 modules=[], 

61 part="part", 

62 top="", 

63 run_index=1, 

64 build_step_hooks=[dummy, files], 

65 ) 

66 

67 assert ( 

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

69 in tcl 

70 ) 

71 assert ( 

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

73 in tcl 

74 ) 

75 

76 

77def test_build_step_hooks_with_same_hook_step(tmp_path): 

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

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

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

81 project_folder=tmp_path / "dummy_project_folder", 

82 modules=[], 

83 part="part", 

84 top="", 

85 run_index=1, 

86 build_step_hooks=[dummy, files], 

87 ) 

88 

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

90 

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

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

93 

94 assert ( 

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

96 ) 

97 

98 

99def test_ip_cache_location(tmp_path): 

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

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

102 ) 

103 assert "config_ip_cache" not in tcl 

104 

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

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

107 ) 

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

109 

110 

111def test_multiple_threads_is_capped_by_vivado_limits(): 

112 num_threads = 128 

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

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

115 ) 

116 print(tcl) 

117 assert "set_param general.maxThreads 32" in tcl 

118 assert "set_param synth.maxThreads 8" in tcl 

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

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

121 

122 

123def test_set_build_run_index(): 

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

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

126 ) 

127 assert "impl_1" in tcl 

128 assert "synth_1" in tcl 

129 assert "impl_2" not in tcl 

130 assert "synth_2" not in tcl 

131 

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

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

134 ) 

135 assert "impl_2" in tcl 

136 assert "synth_2" in tcl 

137 assert "impl_1" not in tcl 

138 assert "synth_1" not in tcl 

139 

140 

141def test_runtime_generics(): 

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

143 project_file=Path(), 

144 output_path=Path(), 

145 num_threads=0, 

146 run_index=0, 

147 generics=dict(dummy=True), 

148 ) 

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

150 assert expected in tcl 

151 

152 

153def test_build_with_synth_only(): 

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

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

156 ) 

157 assert "synth_" in tcl 

158 assert "impl_" in tcl 

159 

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

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

162 ) 

163 assert "synth_" in tcl 

164 assert "impl_" not in tcl 

165 

166 

167def test_build_with_from_impl(): 

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

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

170 ) 

171 assert "synth_" in tcl 

172 assert "impl_" in tcl 

173 

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

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

176 ) 

177 assert "synth_" not in tcl 

178 assert "impl_" in tcl 

179 

180 

181def test_module_getters_are_called_with_correct_arguments(): 

182 modules = [MagicMock(spec=BaseModule)] 

183 VivadoTcl(name="").create( 

184 project_folder=Path(), 

185 modules=modules, 

186 part="", 

187 top="", 

188 run_index=1, 

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

190 ) 

191 

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

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

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

195 

196 

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

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

199class TestVivadoTcl(unittest.TestCase): 

200 

201 tmp_path = None 

202 

203 def setUp(self): 

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

205 

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

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

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

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

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(create_file(self.modules_folder / "apa" / "ip_cores" / "c.tcl")) 

220 

221 # A library with only test files 

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

223 

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

225 

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

227 

228 def test_source_file_list_is_correctly_formatted(self): 

229 tcl = self.tcl.create( 

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

231 ) 

232 

233 # Order of files is not really deterministic 

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

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

236 assert expected_1 in tcl or expected_2 in tcl 

237 

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

239 assert expected in tcl 

240 

241 def test_only_synthesis_files_added_to_create_project_tcl(self): 

242 tcl = self.tcl.create( 

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

244 ) 

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

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

247 

248 def test_constraints(self): 

249 tcl = self.tcl.create( 

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

251 ) 

252 

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

254 assert expected in tcl 

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

256 assert expected in tcl 

257 

258 def test_ip_core_files(self): 

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

260 module = MagicMock(spec=BaseModule) 

261 module.get_ip_core_files.return_value = [ 

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

263 ] 

264 

265 self.modules.append(module) 

266 

267 tcl = self.tcl.create( 

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

269 ) 

270 

271 assert ( 

272 f""" 

273proc create_ip_core_c {{}} {{ 

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

275}} 

276create_ip_core_c 

277""" 

278 in tcl 

279 ) 

280 

281 assert ( 

282 f""" 

283proc create_ip_core_my_name {{}} {{ 

284 set apa "hest" 

285 set zebra "123" 

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

287}} 

288create_ip_core_my_name 

289""" 

290 in tcl 

291 ) 

292 

293 def test_create_with_ip_cores_only(self): 

294 tcl = self.tcl.create( 

295 project_folder=Path(), 

296 modules=self.modules, 

297 part="part", 

298 top="", 

299 run_index=1, 

300 ip_cores_only=True, 

301 ) 

302 assert self.c_tcl in tcl 

303 assert self.a_vhd not in tcl 

304 

305 def test_empty_library_not_in_create_project_tcl(self): 

306 tcl = self.tcl.create( 

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

308 ) 

309 assert "zebra" not in tcl 

310 

311 def test_multiple_tcl_sources(self): 

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

313 tcl = self.tcl.create( 

314 project_folder=Path(), 

315 modules=self.modules, 

316 part="part", 

317 top="", 

318 run_index=1, 

319 tcl_sources=extra_tcl_sources, 

320 ) 

321 

322 for filename in extra_tcl_sources: 

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

324 

325 def test_io_buffer_setting(self): 

326 tcl = self.tcl.create( 

327 project_folder=Path(), 

328 modules=self.modules, 

329 part="part", 

330 top="", 

331 run_index=1, 

332 disable_io_buffers=True, 

333 ) 

334 

335 no_io_buffers_tcl = ( 

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

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

338 ) 

339 assert no_io_buffers_tcl in tcl 

340 

341 tcl = self.tcl.create( 

342 project_folder=Path(), 

343 modules=self.modules, 

344 part="part", 

345 top="", 

346 run_index=1, 

347 disable_io_buffers=False, 

348 ) 

349 

350 assert no_io_buffers_tcl not in tcl 

351 

352 def test_analyze_synthesis_settings_on_and_off(self): 

353 tcl = self.tcl.build( 

354 project_file=Path(), 

355 output_path=Path(), 

356 num_threads=1, 

357 run_index=1, 

358 analyze_synthesis_timing=True, 

359 ) 

360 assert "open_run" in tcl 

361 assert "report_clock_interaction" in tcl 

362 

363 tcl = self.tcl.build( 

364 project_file=Path(), 

365 output_path=Path(), 

366 num_threads=1, 

367 run_index=1, 

368 analyze_synthesis_timing=False, 

369 ) 

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

371 assert "open_run" not in tcl 

372 assert "report_clock_interaction" not in tcl