Coverage for tsfpga/test/test_module.py: 99%

265 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 pathlib import Path 

11from unittest.mock import ANY, MagicMock, patch 

12 

13# Third party libraries 

14import pytest 

15 

16# First party libraries 

17from tsfpga.module import BaseModule, get_module, get_modules 

18from tsfpga.system_utils import create_directory, create_file 

19 

20 

21def test_add_vunit_config_name(): 

22 module = BaseModule(path=Path(), library_name="") 

23 

24 test = MagicMock() 

25 pre_config = MagicMock() 

26 post_check = MagicMock() 

27 

28 module.add_vunit_config(test=test, pre_config=pre_config, post_check=post_check) 

29 test.add_config.assert_called_once_with( 

30 name="test", generics={}, pre_config=pre_config, post_check=post_check 

31 ) 

32 test.reset_mock() 

33 

34 module.add_vunit_config(test=test, name="apa") 

35 test.add_config.assert_called_once_with( 

36 name="apa", generics={}, pre_config=None, post_check=None 

37 ) 

38 test.reset_mock() 

39 

40 module.add_vunit_config(test=test, generics=dict(apa="hest", foo="bar")) 

41 test.add_config.assert_called_once_with( 

42 name="apa_hest.foo_bar", 

43 generics=dict(apa="hest", foo="bar"), 

44 pre_config=None, 

45 post_check=None, 

46 ) 

47 test.reset_mock() 

48 

49 module.add_vunit_config(test=test, name="zebra", generics=dict(apa="hest", foo="bar")) 

50 test.add_config.assert_called_once_with( 

51 name="zebra.apa_hest.foo_bar", 

52 generics=dict(apa="hest", foo="bar"), 

53 pre_config=None, 

54 post_check=None, 

55 ) 

56 

57 

58def test_add_vunit_config_random_seed(): 

59 module = BaseModule(path=Path(), library_name="") 

60 test = MagicMock() 

61 

62 # No seed at all 

63 module.add_vunit_config(test=test) 

64 assert "generics" not in test.add_config.call_args 

65 

66 module.add_vunit_config(test=test, set_random_seed=False) 

67 assert "generics" not in test.add_config.call_args 

68 

69 # No seed, with generics set 

70 module.add_vunit_config(test=test, generics={"apa": "whatever"}) 

71 assert "seed" not in test.add_config.call_args.kwargs["generics"] 

72 

73 # Static seed 

74 module.add_vunit_config(test=test, set_random_seed=0) 

75 assert isinstance(test.add_config.call_args.kwargs["generics"]["seed"], int) 

76 assert test.add_config.call_args.kwargs["generics"]["seed"] == 0 

77 

78 module.add_vunit_config(test=test, set_random_seed=123) 

79 assert isinstance(test.add_config.call_args.kwargs["generics"]["seed"], int) 

80 assert test.add_config.call_args.kwargs["generics"]["seed"] == 123 

81 

82 # Use random seed 

83 module.add_vunit_config(test=test, set_random_seed=True) 

84 assert isinstance(test.add_config.call_args.kwargs["generics"]["seed"], int) 

85 assert test.add_config.call_args.kwargs["generics"]["seed"] >= 0 

86 

87 # Setting explicit value should still work 

88 module.add_vunit_config(test=test, generics={"seed": 711}) 

89 assert test.add_config.call_args.kwargs["generics"]["seed"] == 711 

90 

91 # If a value is already set it will be overwritten 

92 module.add_vunit_config(test=test, generics={"seed": -5}, set_random_seed=True) 

93 assert test.add_config.call_args.kwargs["generics"]["seed"] != -5 

94 

95 

96def test_file_list_filtering(tmp_path): 

97 module_name = "zebra" 

98 path = tmp_path / module_name 

99 

100 create_directory(path / "folder_should_not_be_included") 

101 create_file(path / "should_not_be_included.apa") 

102 

103 synth_files = { 

104 create_file(path / "syn.v"), 

105 create_file(path / "rtl" / "syn.v"), 

106 create_file(path / "src" / "syn.vhd"), 

107 create_file(path / "hdl" / "rtl" / "syn.vhdl"), 

108 create_file(path / "hdl" / "package" / "syn.vhd"), 

109 } 

110 

111 test_files = { 

112 create_file(path / "test" / "test.v"), 

113 create_file(path / "rtl" / "tb" / "test.vhd"), 

114 } 

115 

116 sim_files = {create_file(path / "sim" / "sim.vhd")} 

117 

118 my_module = BaseModule(path=path, library_name="zebra") 

119 

120 files = {file.path for file in my_module.get_synthesis_files()} 

121 assert files == synth_files 

122 

123 files = {file.path for file in my_module.get_simulation_files()} 

124 assert files == synth_files | test_files | sim_files 

125 

126 files = {file.path for file in my_module.get_simulation_files(include_tests=False)} 

127 assert files == synth_files | sim_files 

128 

129 files = {file.path for file in my_module.get_simulation_files(files_include=synth_files)} 

130 assert files == synth_files 

131 

132 files = {file.path for file in my_module.get_simulation_files(files_avoid=synth_files)} 

133 assert files == test_files | sim_files 

134 

135 

136def test_get_synthesis_files_calls_get_simulation_files_with_correct_arguments(): 

137 module = BaseModule(path=Path(), library_name="") 

138 with patch("tsfpga.module.BaseModule.get_synthesis_files") as get_synthesis_files: 

139 module.get_simulation_files( 

140 files_include=True, 

141 files_avoid=False, 

142 apa=123, 

143 include_vhdl_files=1, 

144 include_verilog_files=2, 

145 include_systemverilog_files=3, 

146 ) 

147 get_synthesis_files.assert_called_once_with( 

148 files_include=True, 

149 files_avoid=False, 

150 apa=123, 

151 include_vhdl_files=1, 

152 include_verilog_files=2, 

153 include_systemverilog_files=3, 

154 ) 

155 

156 

157def test_get_vhdl_files(tmp_path): 

158 paths = { 

159 create_file(tmp_path / "apa.vhdl"), 

160 create_file(tmp_path / "apa.vhd"), 

161 } 

162 create_file(tmp_path / "apa.v") 

163 create_file(tmp_path / "apa.vh") 

164 create_file(tmp_path / "apa.sv") 

165 create_file(tmp_path / "apa.svh") 

166 

167 got_hdl_files = BaseModule(path=tmp_path, library_name="").get_synthesis_files( 

168 include_verilog_files=False, include_systemverilog_files=False 

169 ) 

170 assert {hdl_file.path for hdl_file in got_hdl_files} == paths 

171 

172 

173def test_get_verilog_files(tmp_path): 

174 paths = {create_file(tmp_path / "apa.v"), create_file(tmp_path / "apa.vh")} 

175 create_file(tmp_path / "apa.vhdl") 

176 create_file(tmp_path / "apa.vhd") 

177 create_file(tmp_path / "apa.sv") 

178 create_file(tmp_path / "apa.svh") 

179 

180 got_hdl_files = BaseModule(path=tmp_path, library_name="").get_simulation_files( 

181 include_vhdl_files=False, include_systemverilog_files=False 

182 ) 

183 assert {hdl_file.path for hdl_file in got_hdl_files} == paths 

184 

185 

186def test_get_systemverilog_files(tmp_path): 

187 paths = {create_file(tmp_path / "apa.sv"), create_file(tmp_path / "apa.svh")} 

188 create_file(tmp_path / "apa.vhdl") 

189 create_file(tmp_path / "apa.vhd") 

190 create_file(tmp_path / "apa.v") 

191 create_file(tmp_path / "apa.vh") 

192 

193 got_hdl_files = BaseModule(path=tmp_path, library_name="").get_documentation_files( 

194 include_vhdl_files=False, include_verilog_files=False 

195 ) 

196 assert {hdl_file.path for hdl_file in got_hdl_files} == paths 

197 

198 

199def test_get_documentation_files(tmp_path): 

200 module_name = "zebra" 

201 path = tmp_path / module_name 

202 

203 synth_files = { 

204 create_file(path / "rtl" / "syn.v"), 

205 create_file(path / "src" / "syn.vhd"), 

206 } 

207 

208 # Test files 

209 create_file(path / "test" / "test.v") 

210 create_file(path / "rtl" / "tb" / "test.vhd") 

211 

212 sim_files = {create_file(path / "sim" / "sim.vhd")} 

213 

214 module = BaseModule(path=path, library_name="zebra") 

215 

216 # Should include everything except test files 

217 files = {file.path for file in module.get_documentation_files()} 

218 assert files == synth_files | sim_files 

219 

220 

221def test_scoped_constraints(tmp_path): 

222 module_path = tmp_path / "apa" 

223 create_file(module_path / "src" / "hest.vhd") 

224 create_file(module_path / "scoped_constraints" / "hest.tcl") 

225 

226 my_module = BaseModule(module_path, "apa") 

227 scoped_constraints = my_module.get_scoped_constraints() 

228 assert len(scoped_constraints) == 1 

229 assert scoped_constraints[0].ref == "hest" 

230 

231 

232def test_scoped_constraint_entity_not_existing_should_raise_error(tmp_path): 

233 module_path = tmp_path / "apa" 

234 create_file(module_path / "scoped_constraints" / "hest.tcl") 

235 

236 module = BaseModule(module_path, "apa") 

237 with pytest.raises(FileNotFoundError) as exception_info: 

238 module.get_scoped_constraints() 

239 assert str(exception_info.value).startswith("Could not find a matching entity file") 

240 

241 

242def test_can_cast_to_string_without_error(): 

243 str(BaseModule(Path("dummy"), "dummy")) 

244 

245 

246def test_test_case_name(): 

247 assert ( 

248 BaseModule.test_case_name(generics=dict(apa=3, hest_zebra="foo")) == "apa_3.hest_zebra_foo" 

249 ) 

250 assert ( 

251 BaseModule.test_case_name(name="foo", generics=dict(apa=3, hest_zebra="bar")) 

252 == "foo.apa_3.hest_zebra_bar" 

253 ) 

254 

255 

256def test_getting_registers_calls_registers_hook(tmp_path): 

257 with patch("tsfpga.module.from_toml", autospec=True) as from_toml, patch( 

258 "tsfpga.module.BaseModule.registers_hook", autospec=True 

259 ) as registers_hook: 

260 create_file(tmp_path / "a" / "regs_a.toml") 

261 module = BaseModule(path=tmp_path / "a", library_name="a") 

262 registers = module.registers 

263 

264 # TOML file exists so register creation from TOML should run 

265 from_toml.assert_called_once() 

266 registers_hook.assert_called_once() 

267 assert registers is not None 

268 

269 with patch("tsfpga.module.from_toml", autospec=True) as from_toml, patch( 

270 "tsfpga.module.BaseModule.registers_hook", autospec=True 

271 ) as registers_hook: 

272 module = BaseModule(path=tmp_path / "b", library_name="b") 

273 registers = module.registers 

274 

275 # TOML file does not exist, so register creation from TOML should not run 

276 from_toml.assert_not_called() 

277 # Register hook shall still run however 

278 registers_hook.assert_called_once() 

279 assert registers is None 

280 

281 

282def test_creating_synthesis_files_does_not_create_simulation_files(tmp_path): 

283 create_file(tmp_path / "a" / "regs_a.toml", "apa.mode = 'r_w'") 

284 module = BaseModule(path=tmp_path / "a", library_name="a") 

285 

286 synthesis_file = module.register_synthesis_folder / "a_regs_pkg.vhd" 

287 simulation_file = module.register_simulation_folder / "a_register_read_write_pkg.vhd" 

288 

289 module.get_synthesis_files() 

290 assert synthesis_file.exists() 

291 assert not simulation_file.exists() 

292 assert not module.register_simulation_folder.exists() 

293 

294 module.get_simulation_files() 

295 assert simulation_file.exists() 

296 

297 

298def test_old_register_package_should_be_deleted(tmp_path): 

299 create_file(tmp_path / "a" / "regs_a.toml", "apa.mode = 'r_w'") 

300 regs_pkg = create_file(tmp_path / "a" / "a_regs_pkg.vhd") 

301 

302 module = BaseModule(path=tmp_path / "a", library_name="a") 

303 module.get_synthesis_files() 

304 

305 assert not regs_pkg.exists() 

306 

307 

308@pytest.fixture 

309def get_modules_test(tmp_path): 

310 class GetModulesTest: 

311 def __init__(self): 

312 create_directory(tmp_path / "a") 

313 create_directory(tmp_path / "b") 

314 create_directory(tmp_path / "c") 

315 

316 self.modules_folder = tmp_path 

317 self.modules_folders = [self.modules_folder] 

318 

319 return GetModulesTest() 

320 

321 

322# False positive for pytest fixtures 

323# pylint: disable=redefined-outer-name 

324 

325 

326def test_get_module(get_modules_test): 

327 module = get_module(name="a", modules_folder=get_modules_test.modules_folder) 

328 assert module.name == "a" 

329 assert module.library_name == "a" 

330 assert module.path == get_modules_test.modules_folder / "a" 

331 

332 module = get_module( 

333 name="b", 

334 modules_folders=[get_modules_test.modules_folder], 

335 library_name_has_lib_suffix=True, 

336 ) 

337 assert module.name == "b" 

338 assert module.library_name == "b_lib" 

339 assert module.path == get_modules_test.modules_folder / "b" 

340 

341 

342def test_get_module_not_found_should_raise_exception(get_modules_test): 

343 with pytest.raises(RuntimeError) as exception_info: 

344 get_module(name="d", modules_folder=get_modules_test.modules_folder) 

345 assert str(exception_info.value) == 'Could not find module "d".' 

346 

347 

348def test_get_module_found_multiple_should_raise_exception(get_modules_test): 

349 create_directory(get_modules_test.modules_folder / "a" / "x") 

350 create_directory(get_modules_test.modules_folder / "b" / "x") 

351 

352 with pytest.raises(RuntimeError) as exception_info: 

353 get_module( 

354 name="x", 

355 modules_folders=[ 

356 get_modules_test.modules_folder / "a", 

357 get_modules_test.modules_folder / "b", 

358 ], 

359 ) 

360 assert str(exception_info.value) == 'Found multiple modules named "x".' 

361 

362 

363def test_name_filtering_include(get_modules_test): 

364 modules = get_modules( 

365 modules_folders=get_modules_test.modules_folders, names_include=["a", "b"] 

366 ) 

367 assert set(module.name for module in modules) == set(["a", "b"]) 

368 

369 

370def test_name_filtering_avoid(get_modules_test): 

371 modules = get_modules(get_modules_test.modules_folder, names_avoid=["a", "b"]) 

372 assert set(module.name for module in modules) == set(["c"]) 

373 

374 

375def test_name_filtering_include_and_avoid(get_modules_test): 

376 modules = get_modules( 

377 get_modules_test.modules_folder, names_include=["a", "c"], names_avoid=["b", "c"] 

378 ) 

379 assert set(module.name for module in modules) == set(["a"]) 

380 

381 

382def test_library_name_does_not_have_lib_suffix(get_modules_test): 

383 modules = get_modules(get_modules_test.modules_folder) 

384 assert set(module.library_name for module in modules) == set(["a", "b", "c"]) 

385 

386 

387def test_library_name_has_lib_suffix(get_modules_test): 

388 modules = get_modules(get_modules_test.modules_folder, library_name_has_lib_suffix=True) 

389 assert set(module.library_name for module in modules) == set(["a_lib", "b_lib", "c_lib"]) 

390 

391 

392def test_stray_file_can_exist_in_modules_folder_without_error(get_modules_test): 

393 create_file(get_modules_test.modules_folder / "text_file.txt") 

394 modules = get_modules(get_modules_test.modules_folder) 

395 assert len(modules) == 3 

396 

397 

398def test_local_override_of_module_type(get_modules_test): 

399 module_file_content = """ 

400from tsfpga.module import BaseModule 

401 

402class Module(BaseModule): 

403 def id(self): 

404 return """ 

405 

406 create_file(get_modules_test.modules_folder / "a" / "module_a.py", module_file_content + '"a"') 

407 create_file(get_modules_test.modules_folder / "b" / "module_b.py", module_file_content + '"b"') 

408 

409 modules = get_modules(get_modules_test.modules_folder) 

410 

411 assert len(modules) == 3 

412 for module in modules: 

413 if module.name == "a": 

414 assert module.id() == "a" 

415 elif module.name == "b": 

416 assert module.id() == "b" 

417 elif module.name == "c": 

418 assert isinstance(module, BaseModule) 

419 else: 

420 assert False 

421 

422 

423@patch("tsfpga.module.from_toml", autospec=True) 

424@patch("tsfpga.module.VhdlRegisterPackageGenerator.create_if_needed", autospec=True) 

425@patch("tsfpga.module.VhdlRecordPackageGenerator.create_if_needed", autospec=True) 

426@patch("tsfpga.module.VhdlAxiLiteWrapperGenerator.create_if_needed", autospec=True) 

427def test_register_toml_file_parsed_only_once_when_getting_synthesis_files( 

428 create3, create2, create1, from_toml, tmp_path 

429): 

430 toml_file = create_file(tmp_path / "a" / "regs_a.toml") 

431 

432 module = get_modules(tmp_path).get("a") 

433 module.get_synthesis_files() 

434 module.get_synthesis_files() 

435 

436 from_toml.assert_called_once_with("a", toml_file, ANY) 

437 assert create3.call_count == 2 

438 assert create2.call_count == 2 

439 assert create1.call_count == 2 

440 

441 

442@patch("tsfpga.module.from_toml", autospec=True) 

443@patch("tsfpga.module.VhdlRegisterPackageGenerator.create_if_needed", autospec=True) 

444@patch("tsfpga.module.VhdlRecordPackageGenerator.create_if_needed", autospec=True) 

445@patch("tsfpga.module.VhdlAxiLiteWrapperGenerator.create_if_needed", autospec=True) 

446@patch("tsfpga.module.VhdlSimulationReadWritePackageGenerator.create_if_needed", autospec=True) 

447@patch("tsfpga.module.VhdlSimulationCheckPackageGenerator.create_if_needed", autospec=True) 

448@patch("tsfpga.module.VhdlSimulationWaitUntilPackageGenerator.create_if_needed", autospec=True) 

449def test_register_toml_file_parsed_only_once_when_getting_simulation_files( 

450 create6, create5, create4, create3, create2, create1, from_toml, tmp_path 

451): # pylint: disable=too-many-arguments 

452 toml_file = create_file(tmp_path / "a" / "regs_a.toml") 

453 

454 module = get_modules(tmp_path).get("a") 

455 module.get_simulation_files() 

456 module.get_simulation_files() 

457 

458 from_toml.assert_called_once_with("a", toml_file, ANY) 

459 assert create6.call_count == 2 

460 assert create5.call_count == 2 

461 assert create4.call_count == 2 

462 assert create3.call_count == 2 

463 assert create2.call_count == 2 

464 assert create1.call_count == 2 

465 

466 

467@patch("tsfpga.module.from_toml", autospec=True) 

468@patch("tsfpga.module.VhdlRegisterPackageGenerator.create_if_needed", autospec=True) 

469@patch("tsfpga.module.VhdlRecordPackageGenerator.create_if_needed", autospec=True) 

470@patch("tsfpga.module.VhdlAxiLiteWrapperGenerator.create_if_needed", autospec=True) 

471@patch("tsfpga.module.VhdlSimulationReadWritePackageGenerator.create_if_needed", autospec=True) 

472@patch("tsfpga.module.VhdlSimulationCheckPackageGenerator.create_if_needed", autospec=True) 

473@patch("tsfpga.module.VhdlSimulationWaitUntilPackageGenerator.create_if_needed", autospec=True) 

474def test_register_toml_file_parsed_only_once_when_getting_mixed_files( 

475 create6, create5, create4, create3, create2, create1, from_toml, tmp_path 

476): # pylint: disable=too-many-arguments 

477 toml_file = create_file(tmp_path / "a" / "regs_a.toml") 

478 

479 module = get_modules(tmp_path).get("a") 

480 module.get_synthesis_files() 

481 module.get_simulation_files() 

482 

483 from_toml.assert_called_once_with("a", toml_file, ANY) 

484 assert create6.call_count == 1 

485 assert create5.call_count == 1 

486 assert create4.call_count == 1 

487 assert create3.call_count == 2 

488 assert create2.call_count == 2 

489 assert create1.call_count == 2