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

280 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-08-29 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 

9from pathlib import Path 

10from unittest.mock import ANY, MagicMock, patch 

11 

12import pytest 

13 

14from tsfpga.module import BaseModule, get_module, get_modules 

15from tsfpga.system_utils import create_directory, create_file 

16 

17 

18def test_add_vunit_config_name(): 

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

20 

21 test = MagicMock() 

22 pre_config = MagicMock() 

23 post_check = MagicMock() 

24 

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

26 test.add_config.assert_called_once_with( 

27 name="0", generics={}, pre_config=pre_config, post_check=post_check 

28 ) 

29 test.reset_mock() 

30 

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

32 test.add_config.assert_called_once_with( 

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

34 ) 

35 test.reset_mock() 

36 

37 module.add_vunit_config(test=test, generics={"apa": "hest", "foo": "bar"}) 

38 test.add_config.assert_called_once_with( 

39 name="apa_hest.foo_bar", 

40 generics={"apa": "hest", "foo": "bar"}, 

41 pre_config=None, 

42 post_check=None, 

43 ) 

44 test.reset_mock() 

45 

46 module.add_vunit_config(test=test, name="zebra", generics={"apa": "hest", "foo": "bar"}) 

47 test.add_config.assert_called_once_with( 

48 name="zebra.apa_hest.foo_bar", 

49 generics={"apa": "hest", "foo": "bar"}, 

50 pre_config=None, 

51 post_check=None, 

52 ) 

53 

54 

55def test_add_vunit_config_random_seed(): 

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

57 test = MagicMock() 

58 

59 # No seed at all 

60 module.add_vunit_config(test=test) 

61 assert not test.add_config.call_args.kwargs["generics"] 

62 

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

64 assert not test.add_config.call_args.kwargs["generics"] 

65 

66 # No seed, with generics set 

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

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

69 

70 # Static seed 

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

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

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

74 

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

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

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

78 

79 # Use random seed 

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

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

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

83 

84 # Setting explicit value should still work 

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

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

87 

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

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

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

91 

92 

93def test_add_vunit_config_count_1(): 

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

95 test = MagicMock() 

96 

97 module.add_vunit_config(test=test, count=1) 

98 assert test.add_config.call_count == 1 

99 assert test.add_config.call_args.kwargs["name"] == "0" 

100 

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

102 assert test.add_config.call_args.kwargs["name"] == "apa" 

103 

104 

105def test_add_vunit_config_count_2(): 

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

107 test = MagicMock() 

108 

109 module.add_vunit_config(test=test, count=2) 

110 assert test.add_config.call_count == 2 

111 assert test.add_config.call_args_list[0].kwargs["name"] == "0" 

112 assert test.add_config.call_args_list[1].kwargs["name"] == "1" 

113 

114 module.add_vunit_config(test=test, name="apa", count=2) 

115 assert test.add_config.call_args_list[2].kwargs["name"] == "apa.0" 

116 assert test.add_config.call_args_list[3].kwargs["name"] == "apa.1" 

117 

118 module.add_vunit_config(test=test, generics={"apa": True}, count=2) 

119 assert test.add_config.call_args_list[4].kwargs["name"] == "apa_True.0" 

120 assert test.add_config.call_args_list[5].kwargs["name"] == "apa_True.1" 

121 

122 

123def test_file_list_filtering(tmp_path): 

124 module_name = "zebra" 

125 path = tmp_path / module_name 

126 

127 create_directory(path / "folder_should_not_be_included") 

128 create_file(path / "should_not_be_included.apa") 

129 

130 synth_files = { 

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

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

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

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

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

136 } 

137 

138 test_files = { 

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

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

141 } 

142 

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

144 

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

146 

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

148 assert files == synth_files 

149 

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

151 assert files == synth_files | test_files | sim_files 

152 

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

154 assert files == synth_files | sim_files 

155 

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

157 assert files == synth_files 

158 

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

160 assert files == test_files | sim_files 

161 

162 

163def test_get_synthesis_files_calls_get_simulation_files_with_correct_arguments(): 

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

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

166 module.get_simulation_files( 

167 files_include=True, 

168 files_avoid=False, 

169 apa=123, 

170 include_vhdl_files=1, 

171 include_verilog_files=2, 

172 include_systemverilog_files=3, 

173 ) 

174 get_synthesis_files.assert_called_once_with( 

175 files_include=True, 

176 files_avoid=False, 

177 apa=123, 

178 include_vhdl_files=1, 

179 include_verilog_files=2, 

180 include_systemverilog_files=3, 

181 ) 

182 

183 

184def test_get_vhdl_files(tmp_path): 

185 paths = { 

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

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

188 } 

189 create_file(tmp_path / "apa.v") 

190 create_file(tmp_path / "apa.vh") 

191 create_file(tmp_path / "apa.sv") 

192 create_file(tmp_path / "apa.svh") 

193 

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

195 include_verilog_files=False, include_systemverilog_files=False 

196 ) 

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

198 

199 

200def test_get_verilog_files(tmp_path): 

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

202 create_file(tmp_path / "apa.vhdl") 

203 create_file(tmp_path / "apa.vhd") 

204 create_file(tmp_path / "apa.sv") 

205 create_file(tmp_path / "apa.svh") 

206 

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

208 include_vhdl_files=False, include_systemverilog_files=False 

209 ) 

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

211 

212 

213def test_get_systemverilog_files(tmp_path): 

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

215 create_file(tmp_path / "apa.vhdl") 

216 create_file(tmp_path / "apa.vhd") 

217 create_file(tmp_path / "apa.v") 

218 create_file(tmp_path / "apa.vh") 

219 

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

221 include_vhdl_files=False, include_verilog_files=False 

222 ) 

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

224 

225 

226def test_get_documentation_files(tmp_path): 

227 module_name = "zebra" 

228 path = tmp_path / module_name 

229 

230 synth_files = { 

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

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

233 } 

234 

235 # Test files 

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

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

238 

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

240 

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

242 

243 # Should include everything except test files 

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

245 assert files == synth_files | sim_files 

246 

247 

248def test_scoped_constraints(tmp_path): 

249 module_path = tmp_path / "apa" 

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

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

252 

253 my_module = BaseModule(module_path, "apa") 

254 scoped_constraints = my_module.get_scoped_constraints() 

255 assert len(scoped_constraints) == 1 

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

257 

258 

259def test_scoped_constraint_entity_not_existing_should_raise_error(tmp_path): 

260 module_path = tmp_path / "apa" 

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

262 

263 module = BaseModule(module_path, "apa") 

264 with pytest.raises(FileNotFoundError) as exception_info: 

265 module.get_scoped_constraints() 

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

267 

268 

269def test_can_cast_to_string_without_error(): 

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

271 

272 

273def test_test_case_name(): 

274 assert ( 

275 BaseModule.test_case_name(generics={"apa": 3, "hest_zebra": "foo"}) 

276 == "apa_3.hest_zebra_foo" 

277 ) 

278 assert ( 

279 BaseModule.test_case_name(name="foo", generics={"apa": 3, "hest_zebra": "bar"}) 

280 == "foo.apa_3.hest_zebra_bar" 

281 ) 

282 

283 

284def test_getting_registers_calls_registers_hook(tmp_path): 

285 with ( 

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

287 patch("tsfpga.module.BaseModule.registers_hook", autospec=True) as registers_hook, 

288 ): 

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

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

291 registers = module.registers 

292 

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

294 from_toml.assert_called_once() 

295 registers_hook.assert_called_once() 

296 assert registers is not None 

297 

298 with ( 

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

300 patch("tsfpga.module.BaseModule.registers_hook", autospec=True) as registers_hook, 

301 ): 

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

303 registers = module.registers 

304 

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

306 from_toml.assert_not_called() 

307 # Register hook shall still run however 

308 registers_hook.assert_called_once() 

309 assert registers is None 

310 

311 

312def test_creating_synthesis_files_does_not_create_simulation_files(tmp_path): 

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

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

315 

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

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

318 

319 module.get_synthesis_files() 

320 assert synthesis_file.exists() 

321 assert not simulation_file.exists() 

322 assert not module.register_simulation_folder.exists() 

323 

324 module.get_simulation_files() 

325 assert simulation_file.exists() 

326 

327 

328@pytest.fixture 

329def get_modules_test(tmp_path): 

330 class GetModulesTest: 

331 def __init__(self): 

332 create_directory(tmp_path / "a") 

333 create_directory(tmp_path / "b") 

334 create_directory(tmp_path / "c") 

335 

336 self.modules_folder = tmp_path 

337 self.modules_folders = [self.modules_folder] 

338 

339 return GetModulesTest() 

340 

341 

342def test_get_module(get_modules_test): 

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

344 assert module.name == "a" 

345 assert module.library_name == "a" 

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

347 

348 module = get_module( 

349 name="b", 

350 modules_folders=[get_modules_test.modules_folder], 

351 library_name_has_lib_suffix=True, 

352 ) 

353 assert module.name == "b" 

354 assert module.library_name == "b_lib" 

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

356 

357 

358def test_get_module_not_found_should_raise_exception(get_modules_test): 

359 with pytest.raises(RuntimeError) as exception_info: 

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

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

362 

363 

364def test_get_module_found_multiple_should_raise_exception(get_modules_test): 

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

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

367 

368 with pytest.raises(RuntimeError) as exception_info: 

369 get_module( 

370 name="x", 

371 modules_folders=[ 

372 get_modules_test.modules_folder / "a", 

373 get_modules_test.modules_folder / "b", 

374 ], 

375 ) 

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

377 

378 

379def test_name_filtering_include(get_modules_test): 

380 modules = get_modules( 

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

382 ) 

383 assert {module.name for module in modules} == {"a", "b"} 

384 

385 

386def test_name_filtering_avoid(get_modules_test): 

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

388 assert {module.name for module in modules} == {"c"} 

389 

390 

391def test_name_filtering_include_and_avoid(get_modules_test): 

392 modules = get_modules( 

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

394 ) 

395 assert {module.name for module in modules} == {"a"} 

396 

397 

398def test_library_name_does_not_have_lib_suffix(get_modules_test): 

399 modules = get_modules(get_modules_test.modules_folder) 

400 assert {module.library_name for module in modules} == {"a", "b", "c"} 

401 

402 

403def test_library_name_has_lib_suffix(get_modules_test): 

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

405 assert {module.library_name for module in modules} == {"a_lib", "b_lib", "c_lib"} 

406 

407 

408def test_stray_file_can_exist_in_modules_folder_without_error(get_modules_test): 

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

410 modules = get_modules(get_modules_test.modules_folder) 

411 assert len(modules) == 3 

412 

413 

414def test_local_override_of_module_type(get_modules_test): 

415 module_file_content = """ 

416from tsfpga.module import BaseModule 

417 

418class Module(BaseModule): 

419 def id(self): 

420 return """ 

421 

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

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

424 

425 modules = get_modules(get_modules_test.modules_folder) 

426 

427 assert len(modules) == 3 

428 for module in modules: 

429 if module.name == "a": 

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

431 elif module.name == "b": 

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

433 elif module.name == "c": 

434 assert isinstance(module, BaseModule) 

435 else: 

436 raise AssertionError 

437 

438 

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

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

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

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

443def test_register_toml_file_parsed_only_once_when_getting_synthesis_files( 

444 create3, create2, create1, from_toml, tmp_path 

445): 

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

447 

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

449 module.get_synthesis_files() 

450 module.get_synthesis_files() 

451 

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

453 assert create3.call_count == 2 

454 assert create2.call_count == 2 

455 assert create1.call_count == 2 

456 

457 

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

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

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

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

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

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

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

465def test_register_toml_file_parsed_only_once_when_getting_simulation_files( 

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

467): 

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

469 

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

471 module.get_simulation_files() 

472 module.get_simulation_files() 

473 

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

475 assert create6.call_count == 2 

476 assert create5.call_count == 2 

477 assert create4.call_count == 2 

478 assert create3.call_count == 2 

479 assert create2.call_count == 2 

480 assert create1.call_count == 2 

481 

482 

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

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

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

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

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

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

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

490def test_register_toml_file_parsed_only_once_when_getting_mixed_files( 

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

492): 

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

494 

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

496 module.get_synthesis_files() 

497 module.get_simulation_files() 

498 

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

500 assert create6.call_count == 1 

501 assert create5.call_count == 1 

502 assert create4.call_count == 1 

503 assert create3.call_count == 2 

504 assert create2.call_count == 2 

505 assert create1.call_count == 2