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

265 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-02-21 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="test", 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 "generics" not in test.add_config.call_args 

62 

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

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

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_file_list_filtering(tmp_path): 

94 module_name = "zebra" 

95 path = tmp_path / module_name 

96 

97 create_directory(path / "folder_should_not_be_included") 

98 create_file(path / "should_not_be_included.apa") 

99 

100 synth_files = { 

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

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

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

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

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

106 } 

107 

108 test_files = { 

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

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

111 } 

112 

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

114 

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

116 

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

118 assert files == synth_files 

119 

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

121 assert files == synth_files | test_files | sim_files 

122 

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

124 assert files == synth_files | sim_files 

125 

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

127 assert files == synth_files 

128 

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

130 assert files == test_files | sim_files 

131 

132 

133def test_get_synthesis_files_calls_get_simulation_files_with_correct_arguments(): 

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

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

136 module.get_simulation_files( 

137 files_include=True, 

138 files_avoid=False, 

139 apa=123, 

140 include_vhdl_files=1, 

141 include_verilog_files=2, 

142 include_systemverilog_files=3, 

143 ) 

144 get_synthesis_files.assert_called_once_with( 

145 files_include=True, 

146 files_avoid=False, 

147 apa=123, 

148 include_vhdl_files=1, 

149 include_verilog_files=2, 

150 include_systemverilog_files=3, 

151 ) 

152 

153 

154def test_get_vhdl_files(tmp_path): 

155 paths = { 

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

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

158 } 

159 create_file(tmp_path / "apa.v") 

160 create_file(tmp_path / "apa.vh") 

161 create_file(tmp_path / "apa.sv") 

162 create_file(tmp_path / "apa.svh") 

163 

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

165 include_verilog_files=False, include_systemverilog_files=False 

166 ) 

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

168 

169 

170def test_get_verilog_files(tmp_path): 

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

172 create_file(tmp_path / "apa.vhdl") 

173 create_file(tmp_path / "apa.vhd") 

174 create_file(tmp_path / "apa.sv") 

175 create_file(tmp_path / "apa.svh") 

176 

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

178 include_vhdl_files=False, include_systemverilog_files=False 

179 ) 

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

181 

182 

183def test_get_systemverilog_files(tmp_path): 

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

185 create_file(tmp_path / "apa.vhdl") 

186 create_file(tmp_path / "apa.vhd") 

187 create_file(tmp_path / "apa.v") 

188 create_file(tmp_path / "apa.vh") 

189 

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

191 include_vhdl_files=False, include_verilog_files=False 

192 ) 

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

194 

195 

196def test_get_documentation_files(tmp_path): 

197 module_name = "zebra" 

198 path = tmp_path / module_name 

199 

200 synth_files = { 

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

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

203 } 

204 

205 # Test files 

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

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

208 

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

210 

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

212 

213 # Should include everything except test files 

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

215 assert files == synth_files | sim_files 

216 

217 

218def test_scoped_constraints(tmp_path): 

219 module_path = tmp_path / "apa" 

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

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

222 

223 my_module = BaseModule(module_path, "apa") 

224 scoped_constraints = my_module.get_scoped_constraints() 

225 assert len(scoped_constraints) == 1 

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

227 

228 

229def test_scoped_constraint_entity_not_existing_should_raise_error(tmp_path): 

230 module_path = tmp_path / "apa" 

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

232 

233 module = BaseModule(module_path, "apa") 

234 with pytest.raises(FileNotFoundError) as exception_info: 

235 module.get_scoped_constraints() 

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

237 

238 

239def test_can_cast_to_string_without_error(): 

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

241 

242 

243def test_test_case_name(): 

244 assert ( 

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

246 == "apa_3.hest_zebra_foo" 

247 ) 

248 assert ( 

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

250 == "foo.apa_3.hest_zebra_bar" 

251 ) 

252 

253 

254def test_getting_registers_calls_registers_hook(tmp_path): 

255 with ( 

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

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

258 ): 

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

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

261 registers = module.registers 

262 

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

264 from_toml.assert_called_once() 

265 registers_hook.assert_called_once() 

266 assert registers is not None 

267 

268 with ( 

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

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

271 ): 

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 

322def test_get_module(get_modules_test): 

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

324 assert module.name == "a" 

325 assert module.library_name == "a" 

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

327 

328 module = get_module( 

329 name="b", 

330 modules_folders=[get_modules_test.modules_folder], 

331 library_name_has_lib_suffix=True, 

332 ) 

333 assert module.name == "b" 

334 assert module.library_name == "b_lib" 

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

336 

337 

338def test_get_module_not_found_should_raise_exception(get_modules_test): 

339 with pytest.raises(RuntimeError) as exception_info: 

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

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

342 

343 

344def test_get_module_found_multiple_should_raise_exception(get_modules_test): 

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

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

347 

348 with pytest.raises(RuntimeError) as exception_info: 

349 get_module( 

350 name="x", 

351 modules_folders=[ 

352 get_modules_test.modules_folder / "a", 

353 get_modules_test.modules_folder / "b", 

354 ], 

355 ) 

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

357 

358 

359def test_name_filtering_include(get_modules_test): 

360 modules = get_modules( 

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

362 ) 

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

364 

365 

366def test_name_filtering_avoid(get_modules_test): 

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

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

369 

370 

371def test_name_filtering_include_and_avoid(get_modules_test): 

372 modules = get_modules( 

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

374 ) 

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

376 

377 

378def test_library_name_does_not_have_lib_suffix(get_modules_test): 

379 modules = get_modules(get_modules_test.modules_folder) 

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

381 

382 

383def test_library_name_has_lib_suffix(get_modules_test): 

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

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

386 

387 

388def test_stray_file_can_exist_in_modules_folder_without_error(get_modules_test): 

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

390 modules = get_modules(get_modules_test.modules_folder) 

391 assert len(modules) == 3 

392 

393 

394def test_local_override_of_module_type(get_modules_test): 

395 module_file_content = """ 

396from tsfpga.module import BaseModule 

397 

398class Module(BaseModule): 

399 def id(self): 

400 return """ 

401 

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

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

404 

405 modules = get_modules(get_modules_test.modules_folder) 

406 

407 assert len(modules) == 3 

408 for module in modules: 

409 if module.name == "a": 

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

411 elif module.name == "b": 

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

413 elif module.name == "c": 

414 assert isinstance(module, BaseModule) 

415 else: 

416 raise AssertionError 

417 

418 

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

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

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

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

423def test_register_toml_file_parsed_only_once_when_getting_synthesis_files( 

424 create3, create2, create1, from_toml, tmp_path 

425): 

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

427 

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

429 module.get_synthesis_files() 

430 module.get_synthesis_files() 

431 

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

433 assert create3.call_count == 2 

434 assert create2.call_count == 2 

435 assert create1.call_count == 2 

436 

437 

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

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

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

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

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

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

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

445def test_register_toml_file_parsed_only_once_when_getting_simulation_files( 

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

447): 

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

449 

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

451 module.get_simulation_files() 

452 module.get_simulation_files() 

453 

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

455 assert create6.call_count == 2 

456 assert create5.call_count == 2 

457 assert create4.call_count == 2 

458 assert create3.call_count == 2 

459 assert create2.call_count == 2 

460 assert create1.call_count == 2 

461 

462 

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

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

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

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

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

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

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

470def test_register_toml_file_parsed_only_once_when_getting_mixed_files( 

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

472): 

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

474 

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

476 module.get_synthesis_files() 

477 module.get_simulation_files() 

478 

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

480 assert create6.call_count == 1 

481 assert create5.call_count == 1 

482 assert create4.call_count == 1 

483 assert create3.call_count == 2 

484 assert create2.call_count == 2 

485 assert create1.call_count == 2