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 

9from tsfpga.constraint import Constraint 

10from tsfpga.hdl_file import HdlFile 

11from tsfpga.ip_core_file import IpCoreFile 

12from tsfpga.module_list import ModuleList 

13from tsfpga.system_utils import load_python_module 

14from tsfpga.registers.parser import from_toml 

15 

16 

17class BaseModule: 

18 """ 

19 Base class for handling a HDL module with RTL code, constraints, etc. 

20 

21 Files are gathered from a lot of different subfolders, to accommodate for projects having 

22 different catalog structure. 

23 """ 

24 

25 def __init__(self, path, library_name, default_registers=None): 

26 """ 

27 Arguments: 

28 path (`pathlib.Path`): Path to the module folder. 

29 library_name (str): VHDL library name. 

30 default_registers (list(Register)): Default registers. 

31 """ 

32 self.path = path.resolve() 

33 self.name = path.name 

34 self.library_name = library_name 

35 

36 self._default_registers = default_registers 

37 self._registers = None 

38 

39 @staticmethod 

40 def _get_file_list(folders, file_endings, files_include=None, files_avoid=None): 

41 """ 

42 Returns a list of files given a list of folders. 

43 

44 Arguments: 

45 folders (pathlib.Path): The folders to search. 

46 file_endings (tuple(str)): File endings to include. 

47 files_include (set(pathlib.Path)): Optionally filter to only include these files. 

48 files_avoid (set(pathlib.Path)): Optionally filter to discard these files. 

49 """ 

50 files = [] 

51 for folder in folders: 

52 for file in folder.glob("*"): 

53 if not file.is_file(): 

54 continue 

55 

56 if not file.name.lower().endswith(file_endings): 

57 continue 

58 

59 if files_include is not None and file not in files_include: 

60 continue 

61 

62 if files_avoid is not None and file in files_avoid: 

63 continue 

64 

65 files.append(file) 

66 

67 return files 

68 

69 def _get_hdl_file_list(self, folders, files_include, files_avoid): 

70 """ 

71 Return a list of HDL file objects. 

72 """ 

73 return [ 

74 HdlFile(file_path) 

75 for file_path in self._get_file_list( 

76 folders=folders, 

77 file_endings=HdlFile.file_endings, 

78 files_include=files_include, 

79 files_avoid=files_avoid, 

80 ) 

81 ] 

82 

83 @property 

84 def registers(self): 

85 """ 

86 :class:`.RegisterList`: Get the registers for this module. Can be ``None`` if no TOML file 

87 exists and no hook creates registers. 

88 """ 

89 if self._registers is not None: 

90 # Only create object once 

91 return self._registers 

92 

93 toml_file = self.path / f"regs_{self.name}.toml" 

94 if toml_file.exists(): 

95 self._registers = from_toml(self.name, toml_file, self._default_registers) 

96 

97 self.registers_hook() 

98 return self._registers 

99 

100 def registers_hook(self): 

101 """ 

102 This function will be called directly after creating this module's registers from 

103 the TOML definition file. If the TOML file does not exist this hook will still be called, 

104 but the module's registers will be ``None``. 

105 

106 This is a good place if you want to add or modify some registers from Python. 

107 Override this method and implement the desired behavior in a child class. 

108 

109 .. Note:: 

110 This default method does nothing. Shall be overridden by modules that utilize 

111 this mechanism. 

112 """ 

113 

114 def create_regs_vhdl_package(self): 

115 """ 

116 Create a VHDL package in this module with register definitions. 

117 """ 

118 if self.registers is not None: 

119 self.registers.create_vhdl_package(self.path) 

120 

121 # pylint: disable=unused-argument 

122 def get_synthesis_files(self, files_include=None, files_avoid=None, **kwargs): 

123 """ 

124 Get a list of files that shall be included in a synthesis project. 

125 

126 The ``files_include`` and ``files_avoid`` arguments can be used to filter what files are 

127 included. 

128 This can be useful in many situations, e.g. when encrypted files of files that include an 

129 IP core shall be avoided. 

130 It is recommended to overload this function in a child class in your ``module_*.py``, 

131 and call this super method with the arguments supplied. 

132 

133 Arguments: 

134 files_include (set(`pathlib.Path`)): Optionally filter to only include these files. 

135 files_avoid (set(`pathlib.Path`)): Optionally filter to discard these files. 

136 kwargs: Further parameters that can be sent by build flow to control what 

137 files are included. 

138 

139 Return: 

140 list(:class:`.HdlFile`): Files that should be included in a synthesis project. 

141 """ 

142 self.create_regs_vhdl_package() 

143 

144 folders = [ 

145 self.path, 

146 self.path / "src", 

147 self.path / "rtl", 

148 self.path / "hdl" / "rtl", 

149 self.path / "hdl" / "package", 

150 ] 

151 return self._get_hdl_file_list( 

152 folders=folders, files_include=files_include, files_avoid=files_avoid 

153 ) 

154 

155 def get_simulation_files( 

156 self, include_tests=True, files_include=None, files_avoid=None, **kwargs 

157 ): 

158 """ 

159 See :meth:`.get_synthesis_files` for instructions on how to use ``files_include`` 

160 and ``files_avoid``. 

161 

162 Arguments: 

163 include_tests (bool): When ``False`` the ``test`` folder is not included. 

164 The use case is when testing a primary module that depends on other 

165 secondary modules. 

166 In this case we may want to compile the simulation files (``sim`` folder) of the 

167 secondary modules but not their test files (``test`` folder). 

168 

169 .. Note:: 

170 The ``test`` files are considered private to the module and should never be used 

171 by other modules. 

172 files_include (set(`pathlib.Path`)): Optionally filter to only include these files. 

173 files_avoid (set(`pathlib.Path`)): Optionally filter to discard these files. 

174 kwargs: Further parameters that can be sent by simulation flow to control what 

175 files are included. 

176 

177 Return: 

178 list(:class:`.HdlFile`): Files that should be included in a simulation project. 

179 """ 

180 test_folders = [ 

181 self.path / "sim", 

182 ] 

183 

184 if include_tests: 

185 test_folders += [self.path / "rtl" / "tb", self.path / "test"] 

186 

187 synthesis_files = self.get_synthesis_files( 

188 files_include=files_include, files_avoid=files_avoid, **kwargs 

189 ) 

190 test_files = self._get_hdl_file_list( 

191 test_folders, files_include=files_include, files_avoid=files_avoid 

192 ) 

193 

194 return synthesis_files + test_files 

195 

196 def get_formal_files(self, files_include=None, files_avoid=None, **kwargs): 

197 """ 

198 Returns the files to be used for formal verification. 

199 By default these are the same that are used by synthesis 

200 (by calling :meth:`get_synthesis_files`). 

201 Overload this method to select files manually. 

202 

203 See :meth:`.get_synthesis_files` for instructions on how to use ``files_include`` 

204 and ``files_avoid``. 

205 

206 Arguments: 

207 files_include (set(`pathlib.Path`)): Optionally filter to only include these files. 

208 files_avoid (set(`pathlib.Path`)): Optionally filter to discard these files. 

209 kwargs: Further parameters that can be sent by formal flow to control what 

210 files are included. 

211 

212 Return: 

213 list(:class:`.HdlFile`): Files that should be included in a formal verification project. 

214 """ 

215 return self.get_synthesis_files( 

216 files_include=files_include, files_avoid=files_avoid, **kwargs 

217 ) 

218 

219 # pylint: disable=unused-argument 

220 def get_ip_core_files(self, files_include=None, files_avoid=None, **kwargs): 

221 """ 

222 Get IP cores for this module. 

223 

224 Note that the :class:`.ip_core_file.IpCoreFile` class accepts a ``variables`` argument that 

225 can be used to parameterize IP core creation. By overloading this method in a child class 

226 you can pass on ``kwargs`` arguments from the build/simulation flow to 

227 :class:`.ip_core_file.IpCoreFile` creation to achieve this parameterization. 

228 

229 Arguments: 

230 files_include (set(`pathlib.Path`)): Optionally filter to only include these files. 

231 files_avoid (set(`pathlib.Path`)): Optionally filter to discard these files. 

232 kwargs: Further parameters that can be sent by build/simulation flow to control what 

233 IP cores are included and what their variables are. 

234 

235 Return: 

236 list(:class:`.IpCoreFile`): The IP cores for this module. 

237 """ 

238 folders = [ 

239 self.path / "ip_cores", 

240 ] 

241 file_endings = "tcl" 

242 return [ 

243 IpCoreFile(ip_core_file) 

244 for ip_core_file in self._get_file_list( 

245 folders=folders, 

246 file_endings=file_endings, 

247 files_include=files_include, 

248 files_avoid=files_avoid, 

249 ) 

250 ] 

251 

252 # pylint: disable=unused-argument 

253 def get_scoped_constraints(self, files_include=None, files_avoid=None, **kwargs): 

254 """ 

255 Constraints that shall be applied to a certain entity within this module. 

256 

257 Arguments: 

258 files_include (set(`pathlib.Path`)): Optionally filter to only include these files. 

259 files_avoid (set(`pathlib.Path`)): Optionally filter to discard these files. 

260 kwargs: Further parameters that can be sent by build/simulation flow to control what 

261 constraints are included. 

262 

263 Return: 

264 list(:class:`.Constraint`): The constraints. 

265 """ 

266 folders = [ 

267 self.path / "scoped_constraints", 

268 self.path / "entity_constraints", 

269 self.path / "hdl" / "constraints", 

270 ] 

271 file_endings = ("tcl", "xdc") 

272 constraint_files = self._get_file_list( 

273 folders=folders, 

274 file_endings=file_endings, 

275 files_include=files_include, 

276 files_avoid=files_avoid, 

277 ) 

278 

279 constraints = [] 

280 if constraint_files: 

281 synthesis_files = self.get_synthesis_files() 

282 for constraint_file in constraint_files: 

283 # Scoped constraints often depend on clocks having been created by another 

284 # constraint file before they can work. Set processing order to "late" to make 

285 # this more probable. 

286 constraint = Constraint( 

287 constraint_file, scoped_constraint=True, processing_order="late" 

288 ) 

289 constraint.validate_scoped_entity(synthesis_files) 

290 constraints.append(constraint) 

291 return constraints 

292 

293 def setup_vunit(self, vunit_proj, **kwargs): 

294 """ 

295 Setup local configuration of this module's test benches. 

296 

297 .. Note:: 

298 This default method does nothing. Should be overridden by modules that have 

299 any test benches that operate via generics. 

300 

301 Arguments: 

302 vunit_proj: The VUnit project that is used to run simulation. 

303 kwargs: Use this to pass an arbitrary list of arguments from your ``simulate.py`` 

304 to the module where you set up your tests. This could be, e.g., data dimensions, 

305 location of test files, etc. 

306 """ 

307 

308 def setup_formal(self, formal_proj, **kwargs): 

309 """ 

310 Setup this module's formal tests. 

311 

312 .. Note:: 

313 This default method does nothing. Should be overridden by modules that 

314 utilize formal verification. 

315 

316 Arguments: 

317 formal_proj: The formal project that is being used. 

318 kwargs: Use this to pass an arbitrary list of arguments from your ``formal.py`` 

319 to the module where you set up your tests. This could be, e.g., data dimensions, 

320 location of test files, etc. 

321 """ 

322 

323 # pylint: disable=unused-argument, no-self-use 

324 def pre_build(self, project, **kwargs): 

325 """ 

326 This method hook will be called before an FPGA build is run. A typical use case for this 

327 mechanism is to set a register constant or default value based on the generics that 

328 are passed to the project. Could also be used to, e.g., generate BRAM init files 

329 based on project information, etc. 

330 

331 .. Note:: 

332 This default method does nothing. Should be overridden by modules that 

333 utilize this mechanism. 

334 

335 Arguments: 

336 project (.VivadoProject): The project that is being built. 

337 kwargs: All other parameters to the build flow. Includes arguments to 

338 :meth:`.VivadoProject.build` method as well as other arguments set in 

339 :meth:`.VivadoProject.__init__`. 

340 

341 Return: 

342 bool: True if everything went well. 

343 """ 

344 return True 

345 

346 def get_build_projects(self): # pylint: disable=no-self-use 

347 """ 

348 Get FPGA build projects defined by this module. 

349 

350 .. Note:: 

351 This default method does nothing. Should be overridden by modules that set up 

352 build projects. 

353 

354 Return: 

355 list(:class:`.VivadoProject`): FPGA build projects. 

356 """ 

357 return [] 

358 

359 @staticmethod 

360 def test_case_name(name=None, generics=None): 

361 """ 

362 Construct a string suitable for naming test cases. 

363 

364 Example result: MyName.GenericA_ValueA.GenericB_ValueB 

365 """ 

366 if name: 

367 test_case_name = name 

368 else: 

369 test_case_name = "" 

370 

371 if generics: 

372 generics_string = ".".join([f"{key}_{value}" for key, value in generics.items()]) 

373 if test_case_name: 

374 test_case_name = f"{name}.{generics_string}" 

375 else: 

376 test_case_name = generics_string 

377 

378 return test_case_name 

379 

380 def add_vunit_config(self, test, name=None, generics=None, pre_config=None, post_check=None): 

381 """ 

382 Add config for VUnit test case. Wrapper that sets a suitable name. 

383 """ 

384 name = self.test_case_name(name, generics) 

385 test.add_config(name=name, generics=generics, pre_config=pre_config, post_check=post_check) 

386 

387 def __str__(self): 

388 return f"{self.name}:{self.path}" 

389 

390 

391def iterate_module_folders(modules_folders): 

392 for modules_folder in modules_folders: 

393 for module_folder in modules_folder.glob("*"): 

394 if module_folder.is_dir(): 

395 yield module_folder 

396 

397 

398def get_module_object(path, name, library_name_has_lib_suffix, default_registers): 

399 module_file = path / f"module_{name}.py" 

400 library_name = f"{name}_lib" if library_name_has_lib_suffix else name 

401 

402 if module_file.exists(): 

403 return load_python_module(module_file).Module(path, library_name, default_registers) 

404 return BaseModule(path, library_name, default_registers) 

405 

406 

407def get_modules( 

408 modules_folders, 

409 names_include=None, 

410 names_avoid=None, 

411 library_name_has_lib_suffix=False, 

412 default_registers=None, 

413): 

414 """ 

415 Get a list of Module objects based on the source code folders. 

416 

417 Arguments: 

418 modules_folders (list(`pathlib.Path`)): A list of paths where your modules are located. 

419 names_include (list(str)): If specified, only modules with these names will be included. 

420 names_avoid (list(str)): If specified, modules with these names will be discarded. 

421 library_name_has_lib_suffix (bool): If set, the library name will be 

422 ``<module name>_lib``, otherwise it is just ``<module name>``. 

423 default_registers (list(Register)): Default registers. 

424 

425 Return: 

426 :class:`.ModuleList`: List of module objects (:class:`BaseModule` or child classes thereof) 

427 created from the specified folders. 

428 """ 

429 modules = ModuleList() 

430 

431 for module_folder in iterate_module_folders(modules_folders): 

432 module_name = module_folder.name 

433 

434 if names_include is not None and module_name not in names_include: 

435 continue 

436 

437 if names_avoid is not None and module_name in names_avoid: 

438 continue 

439 

440 modules.append( 

441 get_module_object( 

442 module_folder, module_name, library_name_has_lib_suffix, default_registers 

443 ) 

444 ) 

445 

446 return modules