Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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 

9import unittest 

10from unittest.mock import MagicMock, patch 

11from pathlib import Path 

12 

13import pytest 

14 

15from tsfpga.module import BaseModule 

16from tsfpga.system_utils import create_directory, create_file 

17from tsfpga.vivado.project import VivadoProject, copy_and_combine_dicts 

18 

19# pylint: disable=unused-import 

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

21 

22 

23def test_casting_to_string(): 

24 project = VivadoProject(name="my_project", modules=[], part="") 

25 assert ( 

26 str(project) 

27 == """\ 

28my_project 

29Type: VivadoProject 

30Top level: my_project_top 

31Generics: - 

32""" 

33 ) 

34 

35 project = VivadoProject( 

36 name="my_project", modules=[], part="", top="apa", generics=dict(hest=2, zebra=3) 

37 ) 

38 assert ( 

39 str(project) 

40 == """\ 

41my_project 

42Type: VivadoProject 

43Top level: apa 

44Generics: hest=2, zebra=3 

45""" 

46 ) 

47 

48 project = VivadoProject(name="my_project", modules=[], part="", apa=123, hest=456) 

49 assert ( 

50 str(project) 

51 == """\ 

52my_project 

53Type: VivadoProject 

54Top level: my_project_top 

55Generics: - 

56Arguments: apa=123, hest=456 

57""" 

58 ) 

59 

60 

61def test_modules_list_should_be_copied(): 

62 modules = [1] 

63 proj = VivadoProject(name="name", modules=modules, part="part") 

64 

65 modules.append(2) 

66 assert len(proj.modules) == 1 

67 

68 

69def test_static_generics_dictionary_should_be_copied(): 

70 generics = dict(apa=1) 

71 proj = VivadoProject(name="name", modules=[], part="part", generics=generics) 

72 

73 generics["apa"] = 2 

74 assert proj.static_generics["apa"] == 1 

75 

76 

77def test_constraints_list_should_be_copied(): 

78 constraints = [1] 

79 proj = VivadoProject(name="name", modules=[], part="part", constraints=constraints) 

80 

81 constraints.append(2) 

82 assert len(proj.constraints) == 1 

83 

84 

85def test_create_should_raise_exception_if_project_path_already_exists(tmp_path): 

86 create_directory(tmp_path / "projects" / "name") 

87 proj = VivadoProject(name="name", modules=[], part="part") 

88 with pytest.raises(ValueError) as exception_info: 

89 proj.create(tmp_path / "projects") 

90 assert str(exception_info.value).startswith("Folder already exists") 

91 

92 

93def test_build_should_raise_exception_if_project_does_not_exists(tmp_path): 

94 create_directory(tmp_path / "projects") 

95 proj = VivadoProject(name="name", modules=[], part="part") 

96 with pytest.raises(ValueError) as exception_info: 

97 proj.build(tmp_path / "projects", synth_only=True) 

98 assert str(exception_info.value).startswith("Project file does not exist") 

99 

100 

101def test_build_with_impl_run_should_raise_exception_if_no_output_path_is_given(): 

102 proj = VivadoProject(name="name", modules=[], part="part") 

103 with pytest.raises(ValueError) as exception_info: 

104 proj.build("None") 

105 assert str(exception_info.value).startswith("Must specify output_path") 

106 

107 

108def test_top_name(): 

109 assert VivadoProject(name="apa", modules=[], part="").top == "apa_top" 

110 assert VivadoProject(name="apa", modules=[], part="", top="hest").top == "hest" 

111 

112 

113def test_project_file_name_is_same_as_project_name(): 

114 project_path = Path("projects/apa") 

115 assert ( 

116 VivadoProject(name="apa", modules=[], part="").project_file(project_path) 

117 == project_path / "apa.xpr" 

118 ) 

119 

120 

121def test_project_create(tmp_path): 

122 with patch("tsfpga.vivado.project.run_vivado_tcl", autospec=True) as _: 

123 assert VivadoProject(name="apa", modules=[], part="").create(tmp_path / "projects" / "apa") 

124 assert (tmp_path / "projects" / "apa" / "create_vivado_project.tcl").exists() 

125 

126 

127def test_project_create_should_raise_exception_if_project_path_already_exists(tmp_path): 

128 project_path = create_directory(tmp_path / "projects" / "apa") 

129 with pytest.raises(ValueError) as exception_info: 

130 VivadoProject(name="apa", modules=[], part="").create(project_path) 

131 assert str(exception_info.value) == f"Folder already exists: {project_path}" 

132 

133 

134def test_can_cast_project_to_string_without_error(): 

135 print(VivadoProject(name="name", modules=[], part="")) 

136 print( 

137 VivadoProject( 

138 name="name", 

139 modules=[], 

140 part="", 

141 generics=dict(stuff="and", things=True), 

142 defined_at=Path(__file__), 

143 ) 

144 ) 

145 

146 

147def test_copy_and_combine_dict_with_both_arguments_none(): 

148 assert copy_and_combine_dicts(None, None) is None 

149 

150 

151def test_copy_and_combine_dict_with_first_argument_valid(): 

152 dict_first = dict(first=1) 

153 

154 result = copy_and_combine_dicts(dict_first, None) 

155 assert result == dict(first=1) 

156 assert dict_first == dict(first=1) 

157 

158 dict_first["first_dummy"] = True 

159 assert result == dict(first=1) 

160 

161 

162def test_copy_and_combine_dict_with_second_argument_valid(): 

163 dict_second = dict(second=2) 

164 

165 result = copy_and_combine_dicts(None, dict_second) 

166 assert result == dict(second=2) 

167 assert dict_second == dict(second=2) 

168 

169 dict_second["second_dummy"] = True 

170 assert result == dict(second=2) 

171 

172 

173def test_copy_and_combine_dict_with_both_arguments_valid(): 

174 dict_first = dict(first=1) 

175 dict_second = dict(second=2) 

176 

177 result = copy_and_combine_dicts(dict_first, dict_second) 

178 assert result == dict(first=1, second=2) 

179 assert dict_first == dict(first=1) 

180 assert dict_second == dict(second=2) 

181 

182 dict_first["first_dummy"] = True 

183 dict_second["second_dummy"] = True 

184 assert result == dict(first=1, second=2) 

185 

186 

187def test_copy_and_combine_dict_with_both_arguments_valid_and_same_key(): 

188 dict_first = dict(first=1, common=3) 

189 dict_second = dict(second=2, common=4) 

190 

191 result = copy_and_combine_dicts(dict_first, dict_second) 

192 assert result == dict(first=1, second=2, common=4) 

193 assert dict_first == dict(first=1, common=3) 

194 assert dict_second == dict(second=2, common=4) 

195 

196 dict_first["first_dummy"] = True 

197 dict_second["second_dummy"] = True 

198 assert result == dict(first=1, second=2, common=4) 

199 

200 

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

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

203class TestVivadoProject(unittest.TestCase): 

204 

205 tmp_path = None 

206 

207 def setUp(self): 

208 self.project_path = self.tmp_path / "projects" / "apa" / "project" 

209 self.output_path = self.tmp_path / "projects" / "apa" 

210 self.ip_cache_path = MagicMock() 

211 self.build_time_generics = dict(name="value") 

212 self.num_threads = 4 

213 self.run_index = 3 

214 self.synth_only = False 

215 

216 self.mocked_run_vivado_tcl = None 

217 

218 def _create(self, project, **other_arguments): 

219 with patch( 

220 "tsfpga.vivado.project.run_vivado_tcl", autospec=True 

221 ) as self.mocked_run_vivado_tcl: 

222 return project.create( 

223 project_path=self.project_path, ip_cache_path=self.ip_cache_path, **other_arguments 

224 ) 

225 

226 def test_default_pre_create_hook_should_pass(self): 

227 class CustomVivadoProject(VivadoProject): 

228 

229 pass 

230 

231 project = CustomVivadoProject(name="apa", modules=[], part="") 

232 self._create(project) 

233 self.mocked_run_vivado_tcl.assert_called_once() 

234 

235 def test_project_pre_create_hook_returning_false_should_fail_and_not_call_vivado_run(self): 

236 class CustomVivadoProject(VivadoProject): 

237 def pre_create(self, **kwargs): # pylint: disable=no-self-use, unused-argument 

238 return False 

239 

240 assert not self._create(CustomVivadoProject(name="apa", modules=[], part="")) 

241 self.mocked_run_vivado_tcl.assert_not_called() 

242 

243 def test_create_should_call_pre_create_with_correct_parameters(self): 

244 project = VivadoProject(name="apa", modules=[], part="", generics=dict(apa=123), hest=456) 

245 with patch("tsfpga.vivado.project.VivadoProject.pre_create") as mocked_pre_create: 

246 self._create(project, zebra=789) 

247 mocked_pre_create.assert_called_once_with( 

248 project_path=self.project_path, 

249 ip_cache_path=self.ip_cache_path, 

250 part="", 

251 generics=dict(apa=123), 

252 hest=456, 

253 zebra=789, 

254 ) 

255 self.mocked_run_vivado_tcl.assert_called_once() 

256 

257 def _build(self, project): 

258 with patch( 

259 "tsfpga.vivado.project.run_vivado_tcl", autospec=True 

260 ) as self.mocked_run_vivado_tcl, patch( 

261 "tsfpga.vivado.project.VivadoProject._get_size", autospec=True 

262 ) as _, patch( 

263 "tsfpga.vivado.project.shutil.copy2", autospec=True 

264 ) as _: 

265 create_file(self.project_path / "apa.xpr") 

266 return project.build( 

267 project_path=self.project_path, 

268 output_path=self.output_path, 

269 run_index=self.run_index, 

270 generics=self.build_time_generics, 

271 synth_only=self.synth_only, 

272 num_threads=self.num_threads, 

273 other_parameter="hest", 

274 ) 

275 

276 def test_build_module_pre_build_hook_and_create_regs_are_called(self): 

277 project = VivadoProject( 

278 name="apa", 

279 modules=[MagicMock(spec=BaseModule), MagicMock(spec=BaseModule)], 

280 part="", 

281 apa=123, 

282 ) 

283 build_result = self._build(project) 

284 assert build_result.success 

285 

286 for module in project.modules: 

287 module.pre_build.assert_called_once_with( 

288 project=project, 

289 other_parameter="hest", 

290 apa=123, 

291 project_path=self.project_path, 

292 output_path=self.output_path, 

293 run_index=self.run_index, 

294 generics=self.build_time_generics, 

295 synth_only=self.synth_only, 

296 num_threads=self.num_threads, 

297 ) 

298 module.create_regs_vhdl_package.assert_called_once() 

299 

300 def test_default_pre_and_post_build_hooks_should_pass(self): 

301 class CustomVivadoProject(VivadoProject): 

302 

303 pass 

304 

305 build_result = self._build(CustomVivadoProject(name="apa", modules=[], part="")) 

306 assert build_result.success 

307 self.mocked_run_vivado_tcl.assert_called_once() 

308 

309 def test_project_pre_build_hook_returning_false_should_fail_and_not_call_vivado_run(self): 

310 class CustomVivadoProject(VivadoProject): 

311 def pre_build(self, **kwargs): # pylint: disable=no-self-use, unused-argument 

312 return False 

313 

314 build_result = self._build(CustomVivadoProject(name="apa", modules=[], part="")) 

315 assert not build_result.success 

316 self.mocked_run_vivado_tcl.assert_not_called() 

317 

318 def test_project_post_build_hook_returning_false_should_fail(self): 

319 class CustomVivadoProject(VivadoProject): 

320 def post_build(self, **kwargs): # pylint: disable=no-self-use, unused-argument 

321 return False 

322 

323 build_result = self._build(CustomVivadoProject(name="apa", modules=[], part="")) 

324 assert not build_result.success 

325 self.mocked_run_vivado_tcl.assert_called_once() 

326 

327 def test_project_build_hooks_should_be_called_with_correct_parameters(self): 

328 project = VivadoProject( 

329 name="apa", modules=[], part="", generics=dict(static_generic=2), apa=123 

330 ) 

331 with patch("tsfpga.vivado.project.VivadoProject.pre_build") as mocked_pre_build, patch( 

332 "tsfpga.vivado.project.VivadoProject.post_build" 

333 ) as mocked_post_build: 

334 self._build(project) 

335 

336 arguments = dict( 

337 project_path=self.project_path, 

338 output_path=self.output_path, 

339 run_index=self.run_index, 

340 generics=copy_and_combine_dicts(dict(static_generic=2), self.build_time_generics), 

341 synth_only=self.synth_only, 

342 num_threads=self.num_threads, 

343 other_parameter="hest", 

344 apa=123, 

345 ) 

346 mocked_pre_build.assert_called_once_with(**arguments) 

347 

348 # Could be improved by actually checking the build_result object. 

349 # See https://gitlab.com/tsfpga/tsfpga/-/issues/39 

350 arguments.update(build_result=unittest.mock.ANY) 

351 mocked_post_build.assert_called_once_with(**arguments) 

352 

353 def test_module_pre_build_hook_returning_false_should_fail_and_not_call_vivado(self): 

354 module = MagicMock(spec=BaseModule) 

355 module.name = "whatever" 

356 project = VivadoProject(name="apa", modules=[module], part="") 

357 

358 project.modules[0].pre_build.return_value = True 

359 build_result = self._build(project) 

360 assert build_result.success 

361 self.mocked_run_vivado_tcl.assert_called_once() 

362 

363 project.modules[0].pre_build.return_value = False 

364 build_result = self._build(project) 

365 assert not build_result.success 

366 self.mocked_run_vivado_tcl.assert_not_called() 

367 

368 @patch("tsfpga.vivado.project.VivadoTcl", autospec=True) 

369 def test_different_generic_combinations(self, mocked_vivado_tcl): 

370 mocked_vivado_tcl.return_value.build.return_value = "" 

371 

372 # No generics 

373 self.build_time_generics = None 

374 build_result = self._build(VivadoProject(name="apa", modules=[], part="")) 

375 assert build_result.success 

376 # Note: In python 3.8 we can use call_args.kwargs straight away 

377 _, kwargs = mocked_vivado_tcl.return_value.build.call_args 

378 assert kwargs["generics"] == {} 

379 

380 # Only build time generics 

381 self.build_time_generics = dict(runtime="value") 

382 build_result = self._build(VivadoProject(name="apa", modules=[], part="")) 

383 assert build_result.success 

384 _, kwargs = mocked_vivado_tcl.return_value.build.call_args 

385 assert kwargs["generics"] == dict(runtime="value") 

386 

387 # Static and build time generics 

388 self.build_time_generics = dict(runtime="value") 

389 build_result = self._build( 

390 VivadoProject(name="apa", modules=[], part="", generics=dict(static="a value")) 

391 ) 

392 assert build_result.success 

393 _, kwargs = mocked_vivado_tcl.return_value.build.call_args 

394 assert kwargs["generics"] == dict(runtime="value", static="a value") 

395 

396 # Same key in both static and build time generic. Should prefer build time. 

397 self.build_time_generics = dict(static_and_runtime="build value") 

398 build_result = self._build( 

399 VivadoProject( 

400 name="apa", modules=[], part="", generics=dict(static_and_runtime="static value") 

401 ) 

402 ) 

403 assert build_result.success 

404 _, kwargs = mocked_vivado_tcl.return_value.build.call_args 

405 assert kwargs["generics"] == dict(static_and_runtime="build value") 

406 

407 # Only static generics 

408 self.build_time_generics = None 

409 build_result = self._build( 

410 VivadoProject(name="apa", modules=[], part="", generics=dict(runtime="a value")) 

411 ) 

412 assert build_result.success 

413 _, kwargs = mocked_vivado_tcl.return_value.build.call_args 

414 assert kwargs["generics"] == dict(runtime="a value") 

415 

416 @patch("tsfpga.vivado.project.VivadoTcl", autospec=True) 

417 def test_build_time_generics_are_copied(self, mocked_vivado_tcl): 

418 mocked_vivado_tcl.return_value.build.return_value = "" 

419 

420 self.build_time_generics = dict(runtime="value") 

421 build_result = self._build( 

422 VivadoProject(name="apa", modules=[], part="", generics=dict(static="a value")) 

423 ) 

424 assert build_result.success 

425 assert self.build_time_generics == dict(runtime="value") 

426 

427 def test_modules_are_deep_copied_before_pre_create_hook(self): 

428 class CustomVivadoProject(VivadoProject): 

429 def pre_create(self, **kwargs): 

430 self.modules[0].registers = "Some other value" 

431 return True 

432 

433 module = MagicMock(spec=BaseModule) 

434 module.registers = "Some value" 

435 

436 project = CustomVivadoProject(name="apa", modules=[module], part="") 

437 assert self._create(project) 

438 

439 assert module.registers == "Some value" 

440 

441 def test_modules_are_deep_copied_before_pre_build_hook(self): 

442 class CustomVivadoProject(VivadoProject): 

443 def pre_build(self, **kwargs): 

444 self.modules[0].registers = "Some other value" 

445 return True 

446 

447 module = MagicMock(spec=BaseModule) 

448 module.registers = "Some value" 

449 

450 project = CustomVivadoProject(name="apa", modules=[module], part="") 

451 assert self._build(project).success 

452 

453 assert module.registers == "Some value"